/
twelvedata.go
99 lines (83 loc) · 3.03 KB
/
twelvedata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main
// Fetcher for twelvedata API: https://twelvedata.com/docs#exchange-rate
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/monzo/slog"
"github.com/monzo/typhon"
)
const (
twelvedataAPIKeyEnv = "FOREX_EXPORTER_TWELVEDATA_API_KEY"
twelvedataAPIURLTemplate = "https://api.twelvedata.com/currency_conversion?symbol=%s/%s&apikey=%s"
twelvedataAPITimeoutSeconds = 10
// Currently twelvedata has a free quota of 800 API credit per day,
// and each request for a currency pair rate consumes one credit.
// This translates to 2 minutes interval per currency pair.
twelvedataAPIIntervalSecondsPerPair = 120
)
type twelvedataExchangeRateFetcher struct {
apiKey string
client typhon.Service
intervalSeconds int
}
type twelvedataExchangeRateResponse struct {
Symbol string `json:"symbol"`
Rate float64 `json:"rate"`
Timestamp int `json:"timestamp"`
}
func (f *twelvedataExchangeRateFetcher) initFetcher(ctx context.Context, currencyPairs []currencyPair) error {
apiKey := strings.TrimSpace(os.Getenv(twelvedataAPIKeyEnv))
if apiKey == "" {
err := fmt.Errorf("No API key supplied for twelvedata exporter")
return err
}
f.apiKey = apiKey
roundTripper := &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Second * time.Duration(twelvedataAPITimeoutSeconds),
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 60 * time.Second,
TLSHandshakeTimeout: time.Second * time.Duration(twelvedataAPITimeoutSeconds),
ResponseHeaderTimeout: time.Second * time.Duration(twelvedataAPITimeoutSeconds),
ExpectContinueTimeout: 1 * time.Second,
}
f.client = typhon.HttpService(roundTripper).Filter(typhon.ExpirationFilter).Filter(typhon.H2cFilter).Filter(typhon.ErrorFilter)
f.intervalSeconds = twelvedataAPIIntervalSecondsPerPair * len(currencyPairs)
return nil
}
func (f twelvedataExchangeRateFetcher) getIntervalSeconds() int {
return f.intervalSeconds
}
func (f twelvedataExchangeRateFetcher) getName() string {
return "twelvedata"
}
func (f twelvedataExchangeRateFetcher) fetchRate(ctx context.Context, sourceCurrency, targetCurrency string) (*currencyExchangeRate, error) {
requestURL := fmt.Sprintf(twelvedataAPIURLTemplate, sourceCurrency, targetCurrency, f.apiKey)
r := typhon.NewRequest(ctx, http.MethodGet, requestURL, nil).SendVia(f.client).Response()
if r.Error != nil {
slog.Error(ctx, "Received error from twelvedata API: %+v", r.Error)
return nil, r.Error
}
rawResp, err := r.BodyBytes(true)
if err != nil {
slog.Error(ctx, "Error reading response from twelvedata API: %+v", err)
return nil, err
}
var resp = twelvedataExchangeRateResponse{}
if err := json.Unmarshal(rawResp, &resp); err != nil {
slog.Error(ctx, "Error parsing response from twelvedata API: %+v", err)
return nil, err
}
return ¤cyExchangeRate{
Rate: resp.Rate,
Timestamp: resp.Timestamp,
}, nil
}