yahoo.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package feed
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "net/http"
  6. )
  7. type marketResponseJson struct {
  8. Chart struct {
  9. Result []struct {
  10. Meta struct {
  11. Currency string `json:"currency"`
  12. Symbol string `json:"symbol"`
  13. RegularMarketPrice float64 `json:"regularMarketPrice"`
  14. ChartPreviousClose float64 `json:"chartPreviousClose"`
  15. } `json:"meta"`
  16. Indicators struct {
  17. Quote []struct {
  18. Close []float64 `json:"close,omitempty"`
  19. } `json:"quote"`
  20. } `json:"indicators"`
  21. } `json:"result"`
  22. } `json:"chart"`
  23. }
  24. // TODO: allow changing chart time frame
  25. const marketChartDays = 21
  26. func FetchMarketsDataFromYahoo(marketRequests []MarketRequest) (Markets, error) {
  27. requests := make([]*http.Request, 0, len(marketRequests))
  28. for i := range marketRequests {
  29. request, _ := http.NewRequest("GET", fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?range=1mo&interval=1d", marketRequests[i].Symbol), nil)
  30. requests = append(requests, request)
  31. }
  32. job := newJob(decodeJsonFromRequestTask[marketResponseJson](defaultClient), requests)
  33. responses, errs, err := workerPoolDo(job)
  34. if err != nil {
  35. return nil, fmt.Errorf("%w: %v", ErrNoContent, err)
  36. }
  37. markets := make(Markets, 0, len(responses))
  38. var failed int
  39. for i := range responses {
  40. if errs[i] != nil {
  41. failed++
  42. slog.Error("Failed to fetch market data", "symbol", marketRequests[i].Symbol, "error", errs[i])
  43. continue
  44. }
  45. response := responses[i]
  46. if len(response.Chart.Result) == 0 {
  47. failed++
  48. slog.Error("Market response contains no data", "symbol", marketRequests[i].Symbol)
  49. continue
  50. }
  51. prices := response.Chart.Result[0].Indicators.Quote[0].Close
  52. if len(prices) > marketChartDays {
  53. prices = prices[len(prices)-marketChartDays:]
  54. }
  55. previous := response.Chart.Result[0].Meta.RegularMarketPrice
  56. if len(prices) >= 2 && prices[len(prices)-2] != 0 {
  57. previous = prices[len(prices)-2]
  58. }
  59. points := SvgPolylineCoordsFromYValues(100, 50, maybeCopySliceWithoutZeroValues(prices))
  60. currency, exists := currencyToSymbol[response.Chart.Result[0].Meta.Currency]
  61. if !exists {
  62. currency = response.Chart.Result[0].Meta.Currency
  63. }
  64. markets = append(markets, Market{
  65. MarketRequest: marketRequests[i],
  66. Price: response.Chart.Result[0].Meta.RegularMarketPrice,
  67. Currency: currency,
  68. PercentChange: percentChange(
  69. response.Chart.Result[0].Meta.RegularMarketPrice,
  70. previous,
  71. ),
  72. SvgChartPoints: points,
  73. })
  74. }
  75. if len(markets) == 0 {
  76. return nil, ErrNoContent
  77. }
  78. if failed > 0 {
  79. return markets, fmt.Errorf("%w: could not fetch data for %d market(s)", ErrPartialContent, failed)
  80. }
  81. return markets, nil
  82. }