github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/foreign-scrapers/TwelvedataScraper.go (about) 1 package foreignscrapers 2 3 import ( 4 "encoding/json" 5 "errors" 6 "strconv" 7 "strings" 8 "time" 9 10 models "github.com/diadata-org/diadata/pkg/model" 11 "github.com/diadata-org/diadata/pkg/utils" 12 log "github.com/sirupsen/logrus" 13 ) 14 15 const ( 16 sourceTwelvedata = "TwelveData" 17 ) 18 19 var ( 20 twelvedataUpdateSeconds int64 21 twelvedataApiBaseString = "https://api.twelvedata.com/" 22 ) 23 24 type twelvedataAPIFXResponse struct { 25 Symbol string `json:"symbol"` 26 Rate float64 `json:"rate"` 27 Timestamp int64 `json:"timestamp"` 28 } 29 30 type twelvedataAPIStockResponse struct { 31 Price string `json:"price"` 32 } 33 34 type twelvedataQuoteResponse struct { 35 Symbol string `json:"symbol"` 36 Name string `json:"name"` 37 Timestamp int64 `json:"last_quote_at"` 38 Price string `json:"close"` 39 } 40 41 type TwelvedataScraper struct { 42 ticker *time.Ticker 43 foreignScrapper ForeignScraper 44 twelvedataStockSymbols []string 45 twelvedataFXTickers []string 46 twelvedataCommodities []string 47 twelvedataETFs []string 48 apiKey string 49 } 50 51 func init() { 52 var err error 53 twelvedataUpdateSeconds, err = strconv.ParseInt(utils.Getenv("UPDATE_SECONDS", "3600"), 10, 64) 54 if err != nil { 55 log.Error("Parse UPDATE_SECONDS: ", err) 56 } 57 } 58 59 func NewTwelvedataScraper(datastore models.Datastore) *TwelvedataScraper { 60 61 foreignScrapper := ForeignScraper{ 62 shutdown: make(chan nothing), 63 error: nil, 64 datastore: datastore, 65 chanQuotation: make(chan *models.ForeignQuotation), 66 } 67 s := &TwelvedataScraper{ 68 ticker: time.NewTicker(time.Duration(twelvedataUpdateSeconds) * time.Second), 69 foreignScrapper: foreignScrapper, 70 twelvedataStockSymbols: strings.Split(utils.Getenv("STOCK_SYMBOLS", ""), ","), 71 twelvedataFXTickers: strings.Split(utils.Getenv("FX_TICKERS", ""), ","), 72 twelvedataCommodities: strings.Split(utils.Getenv("COMMODITIES", ""), ","), 73 twelvedataETFs: strings.Split(utils.Getenv("ETF", ""), ","), 74 apiKey: utils.Getenv("TWELVEDATA_API_KEY", ""), 75 } 76 77 go s.mainLoop() 78 79 return s 80 81 } 82 83 // mainLoop runs in a goroutine until channel s is closed. 84 func (scraper *TwelvedataScraper) mainLoop() { 85 86 // Initial run. 87 err := scraper.UpdateQuotation() 88 if err != nil { 89 log.Error(err) 90 } 91 92 // Update every @twelvedataUpdateSeconds. 93 for { 94 select { 95 case <-scraper.ticker.C: 96 err := scraper.UpdateQuotation() 97 if err != nil { 98 log.Error(err) 99 } 100 case <-scraper.foreignScrapper.shutdown: // user requested shutdown 101 log.Printf("twelvedatascraper shutting down") 102 return 103 } 104 } 105 106 } 107 108 // Update retrieves new coin information from the twelvedata API and stores it to influx 109 func (scraper *TwelvedataScraper) UpdateQuotation() error { 110 111 log.Printf("Executing stock data update for %v symbols", len(scraper.twelvedataStockSymbols)) 112 for _, symbol := range scraper.twelvedataStockSymbols { 113 if symbol == "" { 114 continue 115 } 116 quotation, err := scraper.getTwelveStockData(symbol) 117 if err != nil { 118 log.Error("getTwelveStockData: ", err) 119 } 120 price, err := strconv.ParseFloat(quotation.Price, 64) 121 if err != nil { 122 log.Error("Parse Float for: ", symbol) 123 } 124 125 foreignQuotation := models.ForeignQuotation{ 126 Symbol: symbol, 127 Price: price, 128 Source: sourceTwelvedata, 129 Time: time.Now(), 130 } 131 scraper.foreignScrapper.chanQuotation <- &foreignQuotation 132 } 133 134 log.Printf("Executing fx data update for %v symbols", len(scraper.twelvedataFXTickers)) 135 for _, ticker := range scraper.twelvedataFXTickers { 136 if ticker == "" { 137 continue 138 } 139 quotation, err := scraper.getTwelveFXData(ticker) 140 if err != nil { 141 log.Error("getTwelveFXData: ", err) 142 } 143 144 foreignQuotation := models.ForeignQuotation{ 145 Symbol: ticker, 146 Price: quotation.Rate, 147 Source: sourceTwelvedata, 148 Time: time.Unix(quotation.Timestamp, 0), 149 } 150 scraper.foreignScrapper.chanQuotation <- &foreignQuotation 151 } 152 153 log.Printf("Executing commodities data update for %v symbols", len(scraper.twelvedataCommodities)) 154 for _, ticker := range scraper.twelvedataCommodities { 155 if ticker == "" { 156 continue 157 } 158 quotation, err := scraper.getTwelveQuote(ticker) 159 if err != nil { 160 log.Error("getTwelveFXData: ", err) 161 } 162 163 price, err := strconv.ParseFloat(quotation.Price, 64) 164 if err != nil { 165 log.Errorf("parse price for %s", quotation.Symbol) 166 } 167 168 foreignQuotation := models.ForeignQuotation{ 169 Symbol: ticker, 170 Name: quotation.Name, 171 Price: price, 172 Source: sourceTwelvedata, 173 Time: time.Unix(quotation.Timestamp, 0), 174 } 175 scraper.foreignScrapper.chanQuotation <- &foreignQuotation 176 } 177 178 log.Printf("Executing ETF data update for %v symbols", len(scraper.twelvedataETFs)) 179 for _, ticker := range scraper.twelvedataETFs { 180 if ticker == "" { 181 continue 182 } 183 quotation, err := scraper.getTwelveQuote(ticker) 184 if err != nil { 185 log.Error("getTwelveFXData: ", err) 186 } 187 188 price, err := strconv.ParseFloat(quotation.Price, 64) 189 if err != nil { 190 log.Errorf("parse price for %s", quotation.Symbol) 191 } 192 193 foreignQuotation := models.ForeignQuotation{ 194 Symbol: ticker, 195 Name: quotation.Name, 196 Price: price, 197 Source: sourceTwelvedata, 198 Time: time.Unix(quotation.Timestamp, 0), 199 } 200 scraper.foreignScrapper.chanQuotation <- &foreignQuotation 201 } 202 203 return nil 204 205 } 206 207 func (scraper *TwelvedataScraper) GetQuoteChannel() chan *models.ForeignQuotation { 208 return scraper.foreignScrapper.chanQuotation 209 } 210 211 func (scraper *TwelvedataScraper) getTwelveFXData(symbol string) (fxRate twelvedataAPIFXResponse, err error) { 212 var response []byte 213 214 apiURL := twelvedataApiBaseString + "exchange_rate?symbol=" + symbol + "&apikey=" + scraper.apiKey 215 response, _, err = utils.GetRequest(apiURL) 216 if err != nil { 217 return 218 } 219 220 err = json.Unmarshal(response, &fxRate) 221 return 222 } 223 224 func (scraper *TwelvedataScraper) getTwelveStockData(symbol string) (stockPrice twelvedataAPIStockResponse, err error) { 225 var response []byte 226 227 apiURL := twelvedataApiBaseString + "price?symbol=" + symbol + "&apikey=" + scraper.apiKey 228 response, _, err = utils.GetRequest(apiURL) 229 if err != nil { 230 return 231 } 232 233 err = json.Unmarshal(response, &stockPrice) 234 return 235 } 236 237 func (scraper *TwelvedataScraper) getTwelveQuote(symbol string) (commodity twelvedataQuoteResponse, err error) { 238 var response []byte 239 240 apiURL := twelvedataApiBaseString + "quote?symbol=" + symbol + "&apikey=" + scraper.apiKey 241 response, _, err = utils.GetRequest(apiURL) 242 if err != nil { 243 return 244 } 245 246 err = json.Unmarshal(response, &commodity) 247 return 248 } 249 250 // Close closes any existing API connections 251 func (scraper *TwelvedataScraper) Close() error { 252 if scraper.foreignScrapper.closed { 253 return errors.New("scraper already closed") 254 } 255 close(scraper.foreignScrapper.shutdown) 256 <-scraper.foreignScrapper.shutdownDone 257 scraper.foreignScrapper.errorLock.RLock() 258 defer scraper.foreignScrapper.errorLock.RUnlock() 259 return scraper.foreignScrapper.error 260 }