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  }