diff --git a/marketdata/rest.go b/marketdata/rest.go index 370b534..ce80602 100644 --- a/marketdata/rest.go +++ b/marketdata/rest.go @@ -135,6 +135,11 @@ type baseRequest struct { Sort Sort } +type basePaginatedRequest struct { + baseRequest + PageToken string +} + func (c *Client) setBaseQuery(q url.Values, req baseRequest) { q.Set("symbols", strings.Join(req.Symbols, ",")) if !req.Start.IsZero() { @@ -161,6 +166,13 @@ func (c *Client) setBaseQuery(q url.Values, req baseRequest) { } } +func (c *Client) setBasePaginatedQuery(q url.Values, req basePaginatedRequest) { + c.setBaseQuery(q, req.baseRequest) + if req.PageToken != "" { + q.Set("page_token", req.PageToken) + } +} + const ( v2MaxLimit = 10000 newsMaxLimit = 50 @@ -208,47 +220,76 @@ type GetTradesRequest struct { Sort Sort } +// GetTradesPaginatedRequest contains optional parameters for getting trades in a paginated way. +type GetTradesPaginatedRequest struct { + GetTradesRequest + // PageToken is the pagination token to continue from + PageToken string +} + // GetTrades returns the trades for the given symbol. func (c *Client) GetTrades(symbol string, req GetTradesRequest) ([]Trade, error) { - resp, err := c.GetMultiTrades([]string{symbol}, req) + resp, _, err := c.GetTradesPaginated(symbol, GetTradesPaginatedRequest{GetTradesRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetTradesPaginated returns the trades for the given symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetTradesPaginated(symbol string, req GetTradesPaginatedRequest) ([]Trade, string, error) { + resp, nextPageToken, err := c.GetMultiTradesPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetMultiTrades returns trades for the given symbols. func (c *Client) GetMultiTrades(symbols []string, req GetTradesRequest) (map[string][]Trade, error) { - u, err := url.Parse(fmt.Sprintf("%s/%s/trades", c.opts.BaseURL, stockPrefix)) + resp, _, err := c.GetMultiTradesPaginated(symbols, GetTradesPaginatedRequest{GetTradesRequest: req}) if err != nil { return nil, err } + return resp, nil +} + +// GetMultiTradesPaginated returns trades for the given symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetMultiTradesPaginated(symbols []string, req GetTradesPaginatedRequest) (map[string][]Trade, string, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/trades", c.opts.BaseURL, stockPrefix)) + if err != nil { + return nil, "", err + } q := u.Query() - c.setBaseQuery(q, baseRequest{ - Symbols: symbols, - Start: req.Start, - End: req.End, - Feed: req.Feed, - AsOf: req.AsOf, - Currency: req.Currency, - Sort: req.Sort, + c.setBasePaginatedQuery(q, basePaginatedRequest{ + baseRequest: baseRequest{ + Symbols: symbols, + Start: req.Start, + End: req.End, + Feed: req.Feed, + AsOf: req.AsOf, + Currency: req.Currency, + Sort: req.Sort, + }, + PageToken: req.PageToken, }) trades := make(map[string][]Trade, len(symbols)) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var tradeResp multiTradeResponse if err = unmarshal(resp, &tradeResp); err != nil { - return nil, err + return nil, "", err } for symbol, t := range tradeResp.Trades { @@ -256,11 +297,13 @@ func (c *Client) GetMultiTrades(symbols []string, req GetTradesRequest) (map[str received += len(t) } if tradeResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *tradeResp.NextPageToken q.Set("page_token", *tradeResp.NextPageToken) } - return trades, nil + return trades, nextPageToken, nil } // GetQuotesRequest contains optional parameters for getting quotes @@ -284,47 +327,76 @@ type GetQuotesRequest struct { Sort Sort } +// GetQuotesPaginatedRequest contains optional parameters for getting quotes in a paginated way +type GetQuotesPaginatedRequest struct { + GetQuotesRequest + // PageToken is the pagination token to continue from + PageToken string +} + // GetQuotes returns the quotes for the given symbol. func (c *Client) GetQuotes(symbol string, req GetQuotesRequest) ([]Quote, error) { - resp, err := c.GetMultiQuotes([]string{symbol}, req) + resp, _, err := c.GetQuotesPaginated(symbol, GetQuotesPaginatedRequest{GetQuotesRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetQuotesPaginated returns quotes for the given symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetQuotesPaginated(symbol string, req GetQuotesPaginatedRequest) ([]Quote, string, error) { + resp, nextPageToken, err := c.GetMultiQuotesPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetMultiQuotes returns quotes for the given symbols. func (c *Client) GetMultiQuotes(symbols []string, req GetQuotesRequest) (map[string][]Quote, error) { - u, err := url.Parse(fmt.Sprintf("%s/%s/quotes", c.opts.BaseURL, stockPrefix)) + resp, _, err := c.GetMultiQuotesPaginated(symbols, GetQuotesPaginatedRequest{GetQuotesRequest: req}) if err != nil { return nil, err } + return resp, nil +} + +// GetMultiQuotesPaginated returns quotes for the given symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetMultiQuotesPaginated(symbols []string, req GetQuotesPaginatedRequest) (map[string][]Quote, string, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/quotes", c.opts.BaseURL, stockPrefix)) + if err != nil { + return nil, "", err + } q := u.Query() - c.setBaseQuery(q, baseRequest{ - Symbols: symbols, - Start: req.Start, - End: req.End, - Feed: req.Feed, - AsOf: req.AsOf, - Currency: req.Currency, - Sort: req.Sort, + c.setBasePaginatedQuery(q, basePaginatedRequest{ + baseRequest: baseRequest{ + Symbols: symbols, + Start: req.Start, + End: req.End, + Feed: req.Feed, + AsOf: req.AsOf, + Currency: req.Currency, + Sort: req.Sort, + }, + PageToken: req.PageToken, }) quotes := make(map[string][]Quote, len(symbols)) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var quoteResp multiQuoteResponse if err = unmarshal(resp, "eResp); err != nil { - return nil, err + return nil, "", err } for symbol, q := range quoteResp.Quotes { @@ -332,11 +404,13 @@ func (c *Client) GetMultiQuotes(symbols []string, req GetQuotesRequest) (map[str received += len(q) } if quoteResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *quoteResp.NextPageToken q.Set("page_token", *quoteResp.NextPageToken) } - return quotes, nil + return quotes, nextPageToken, nil } // GetBarsRequest contains optional parameters for getting bars @@ -365,6 +439,13 @@ type GetBarsRequest struct { Sort Sort } +// GetBarsPaginatedRequest contains optional parameters for getting bars in a paginated way +type GetBarsPaginatedRequest struct { + GetBarsRequest + // PageToken is the pagination token to continue from + PageToken string +} + func (c *Client) setQueryBarRequest(q url.Values, symbols []string, req GetBarsRequest) { c.setBaseQuery(q, baseRequest{ Symbols: symbols, @@ -387,40 +468,66 @@ func (c *Client) setQueryBarRequest(q url.Values, symbols []string, req GetBarsR q.Set("timeframe", timeframe.String()) } +func (c *Client) setQueryBarPaginatedRequest(q url.Values, symbols []string, req GetBarsPaginatedRequest) { + c.setQueryBarRequest(q, symbols, req.GetBarsRequest) + if req.PageToken != "" { + q.Set("page_token", req.PageToken) + } +} + // GetBars returns a slice of bars for the given symbol. func (c *Client) GetBars(symbol string, req GetBarsRequest) ([]Bar, error) { - resp, err := c.GetMultiBars([]string{symbol}, req) + resp, _, err := c.GetBarsPaginated(symbol, GetBarsPaginatedRequest{GetBarsRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetBarsPaginated returns bars for the given symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetBarsPaginated(symbol string, req GetBarsPaginatedRequest) ([]Bar, string, error) { + resp, nextPageToken, err := c.GetMultiBarsPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetMultiBars returns bars for the given symbols. func (c *Client) GetMultiBars(symbols []string, req GetBarsRequest) (map[string][]Bar, error) { + resp, _, err := c.GetMultiBarsPaginated(symbols, GetBarsPaginatedRequest{GetBarsRequest: req}) + if err != nil { + return nil, err + } + return resp, nil +} + +// GetMultiBarsPaginated returns bars for the given symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetMultiBarsPaginated(symbols []string, req GetBarsPaginatedRequest) (map[string][]Bar, string, error) { bars := make(map[string][]Bar, len(symbols)) u, err := url.Parse(fmt.Sprintf("%s/%s/bars", c.opts.BaseURL, stockPrefix)) if err != nil { - return nil, err + return nil, "", err } q := u.Query() - c.setQueryBarRequest(q, symbols, req) + c.setQueryBarPaginatedRequest(q, symbols, req) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var barResp multiBarResponse if err = unmarshal(resp, &barResp); err != nil { - return nil, err + return nil, "", err } for symbol, b := range barResp.Bars { @@ -428,11 +535,13 @@ func (c *Client) GetMultiBars(symbols []string, req GetBarsRequest) (map[string] received += len(b) } if barResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *barResp.NextPageToken q.Set("page_token", *barResp.NextPageToken) } - return bars, nil + return bars, nextPageToken, nil } // GetAuctionsRequest contains optional parameters for getting auctions @@ -454,49 +563,80 @@ type GetAuctionsRequest struct { Sort Sort } +// GetAuctionsPaginatedRequest contains optional parameters for getting auctions in a paginated way +type GetAuctionsPaginatedRequest struct { + GetAuctionsRequest + // PageToken is the pagination token to continue from + PageToken string +} + // GetAuctions returns the auctions for the given symbol. func (c *Client) GetAuctions(symbol string, req GetAuctionsRequest) ([]DailyAuctions, error) { - resp, err := c.GetMultiAuctions([]string{symbol}, req) + resp, _, err := c.GetAuctionsPaginated(symbol, GetAuctionsPaginatedRequest{GetAuctionsRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetAuctionsPaginated returns auctions for the given symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetAuctionsPaginated(symbol string, req GetAuctionsPaginatedRequest) ([]DailyAuctions, string, error) { + resp, nextPageToken, err := c.GetMultiAuctionsPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetMultiAuctions returns auctions for the given symbols. func (c *Client) GetMultiAuctions( symbols []string, req GetAuctionsRequest, ) (map[string][]DailyAuctions, error) { - u, err := url.Parse(fmt.Sprintf("%s/%s/auctions", c.opts.BaseURL, stockPrefix)) + resp, _, err := c.GetMultiAuctionsPaginated(symbols, GetAuctionsPaginatedRequest{GetAuctionsRequest: req}) if err != nil { return nil, err } + return resp, nil +} + +// GetMultiAuctionsPaginated returns auctions for the given symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetMultiAuctionsPaginated( + symbols []string, req GetAuctionsPaginatedRequest, +) (map[string][]DailyAuctions, string, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/auctions", c.opts.BaseURL, stockPrefix)) + if err != nil { + return nil, "", err + } q := u.Query() - c.setBaseQuery(q, baseRequest{ - Symbols: symbols, - Start: req.Start, - End: req.End, - Feed: "sip", - AsOf: req.AsOf, - Currency: req.Currency, - Sort: req.Sort, + c.setBasePaginatedQuery(q, basePaginatedRequest{ + baseRequest: baseRequest{ + Symbols: symbols, + Start: req.Start, + End: req.End, + Feed: "sip", + AsOf: req.AsOf, + Currency: req.Currency, + Sort: req.Sort, + }, + PageToken: req.PageToken, }) auctions := make(map[string][]DailyAuctions, len(symbols)) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var auctionsResp multiAuctionsResponse if err = unmarshal(resp, &auctionsResp); err != nil { - return nil, err + return nil, "", err } for symbol, a := range auctionsResp.Auctions { @@ -504,11 +644,13 @@ func (c *Client) GetMultiAuctions( received += len(a) } if auctionsResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *auctionsResp.NextPageToken q.Set("page_token", *auctionsResp.NextPageToken) } - return auctions, nil + return auctions, nextPageToken, nil } type baseLatestRequest struct { @@ -722,6 +864,13 @@ func setCryptoBaseQuery(q url.Values, req cryptoBaseRequest) { } } +func setCryptoBasePaginatedQuery(q url.Values, req cryptoBaseRequest, pageToken string) { + setCryptoBaseQuery(q, req) + if pageToken != "" { + q.Set("page_token", pageToken) + } +} + // GetCryptoTradesRequest contains optional parameters for getting crypto trades type GetCryptoTradesRequest struct { // Start is the inclusive beginning of the interval @@ -739,44 +888,70 @@ type GetCryptoTradesRequest struct { Sort Sort } +// GetCryptoTradesPaginatedRequest contains optional parameters for getting crypto trades in a paginated way +type GetCryptoTradesPaginatedRequest struct { + GetCryptoTradesRequest + // PageToken is the pagination token to continue from + PageToken string +} + // GetCryptoTrades returns the trades for the given crypto symbol. func (c *Client) GetCryptoTrades(symbol string, req GetCryptoTradesRequest) ([]CryptoTrade, error) { - resp, err := c.GetCryptoMultiTrades([]string{symbol}, req) + resp, _, err := c.GetCryptoTradesPaginated(symbol, GetCryptoTradesPaginatedRequest{GetCryptoTradesRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetCryptoTradesPaginated returns trades for the given crypto symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetCryptoTradesPaginated(symbol string, req GetCryptoTradesPaginatedRequest) ([]CryptoTrade, string, error) { + resp, nextPageToken, err := c.GetCryptoMultiTradesPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetMultiTrades returns trades for the given crypto symbols. func (c *Client) GetCryptoMultiTrades(symbols []string, req GetCryptoTradesRequest) (map[string][]CryptoTrade, error) { - u, err := url.Parse(fmt.Sprintf("%s/%s/%s/trades", c.opts.BaseURL, cryptoPrefix, c.cryptoFeed(req.CryptoFeed))) + resp, _, err := c.GetCryptoMultiTradesPaginated(symbols, GetCryptoTradesPaginatedRequest{GetCryptoTradesRequest: req}) if err != nil { return nil, err } + return resp, nil +} + +// GetCryptoMultiTradesPaginated returns trades for the given crypto symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetCryptoMultiTradesPaginated(symbols []string, req GetCryptoTradesPaginatedRequest) (map[string][]CryptoTrade, string, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/%s/trades", c.opts.BaseURL, cryptoPrefix, c.cryptoFeed(req.CryptoFeed))) + if err != nil { + return nil, "", err + } q := u.Query() - setCryptoBaseQuery(q, cryptoBaseRequest{ + setCryptoBasePaginatedQuery(q, cryptoBaseRequest{ Symbols: symbols, Start: req.Start, End: req.End, Sort: req.Sort, - }) + }, req.PageToken) trades := make(map[string][]CryptoTrade, len(symbols)) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var tradeResp cryptoMultiTradeResponse if err = unmarshal(resp, &tradeResp); err != nil { - return nil, err + return nil, "", err } for symbol, t := range tradeResp.Trades { @@ -784,11 +959,13 @@ func (c *Client) GetCryptoMultiTrades(symbols []string, req GetCryptoTradesReque received += len(t) } if tradeResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *tradeResp.NextPageToken q.Set("page_token", *tradeResp.NextPageToken) } - return trades, nil + return trades, nextPageToken, nil } // GetCryptoQuotesRequest contains optional parameters for getting crypto quotes @@ -879,6 +1056,13 @@ type GetCryptoBarsRequest struct { Sort Sort } +// GetCryptoBarsPaginatedRequest contains optional parameters for getting crypto bars in a paginated way +type GetCryptoBarsPaginatedRequest struct { + GetCryptoBarsRequest + // PageToken is the pagination token to continue from + PageToken string +} + func setQueryCryptoBarRequest(q url.Values, symbols []string, req GetCryptoBarsRequest) { setCryptoBaseQuery(q, cryptoBaseRequest{ Symbols: symbols, @@ -893,40 +1077,66 @@ func setQueryCryptoBarRequest(q url.Values, symbols []string, req GetCryptoBarsR q.Set("timeframe", timeframe.String()) } +func setQueryCryptoBarPaginatedRequest(q url.Values, symbols []string, req GetCryptoBarsPaginatedRequest) { + setQueryCryptoBarRequest(q, symbols, req.GetCryptoBarsRequest) + if req.PageToken != "" { + q.Set("page_token", req.PageToken) + } +} + // GetCryptoBars returns a slice of bars for the given crypto symbol. func (c *Client) GetCryptoBars(symbol string, req GetCryptoBarsRequest) ([]CryptoBar, error) { - resp, err := c.GetCryptoMultiBars([]string{symbol}, req) + resp, _, err := c.GetCryptoBarsPaginated(symbol, GetCryptoBarsPaginatedRequest{GetCryptoBarsRequest: req}) if err != nil { return nil, err } - return resp[symbol], nil + return resp, nil +} + +// GetCryptoBarsPaginated returns bars for the given crypto symbol, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetCryptoBarsPaginated(symbol string, req GetCryptoBarsPaginatedRequest) ([]CryptoBar, string, error) { + resp, nextPageToken, err := c.GetCryptoMultiBarsPaginated([]string{symbol}, req) + if err != nil { + return nil, nextPageToken, err + } + return resp[symbol], nextPageToken, nil } // GetCryptoMultiBars returns bars for the given crypto symbols. func (c *Client) GetCryptoMultiBars(symbols []string, req GetCryptoBarsRequest) (map[string][]CryptoBar, error) { + resp, _, err := c.GetCryptoMultiBarsPaginated(symbols, GetCryptoBarsPaginatedRequest{GetCryptoBarsRequest: req}) + if err != nil { + return nil, err + } + return resp, nil +} + +// GetCryptoMultiBarsPaginated returns bars for the given crypto symbols, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetCryptoMultiBarsPaginated(symbols []string, req GetCryptoBarsPaginatedRequest) (map[string][]CryptoBar, string, error) { u, err := url.Parse(fmt.Sprintf("%s/%s/%s/bars", c.opts.BaseURL, cryptoPrefix, c.cryptoFeed(req.CryptoFeed))) if err != nil { - return nil, err + return nil, "", err } q := u.Query() - setQueryCryptoBarRequest(q, symbols, req) + setQueryCryptoBarPaginatedRequest(q, symbols, req) bars := make(map[string][]CryptoBar, len(symbols)) received := 0 + nextPageToken := req.PageToken for req.TotalLimit == 0 || received < req.TotalLimit { setQueryLimit(q, req.TotalLimit, req.PageLimit, received, v2MaxLimit) u.RawQuery = q.Encode() resp, err := c.get(u) if err != nil { - return nil, err + return nil, "", err } var barResp cryptoMultiBarResponse if err = unmarshal(resp, &barResp); err != nil { - return nil, err + return nil, "", err } for symbol, b := range barResp.Bars { @@ -934,11 +1144,13 @@ func (c *Client) GetCryptoMultiBars(symbols []string, req GetCryptoBarsRequest) received += len(b) } if barResp.NextPageToken == nil { + nextPageToken = "" break } + nextPageToken = *barResp.NextPageToken q.Set("page_token", *barResp.NextPageToken) } - return bars, nil + return bars, nextPageToken, nil } type cryptoBaseLatestRequest struct { @@ -1165,6 +1377,13 @@ type GetNewsRequest struct { PageLimit int } +// GetNewsPaginatedRequest contains optional parameters for getting news articles in a paginated way. +type GetNewsPaginatedRequest struct { + GetNewsRequest + // PageToken is the pagination token to continue to next page + PageToken string +} + func (c *Client) setNewsQuery(q url.Values, p GetNewsRequest) { if len(p.Symbols) > 0 { q.Set("symbols", strings.Join(p.Symbols, ",")) @@ -1186,30 +1405,47 @@ func (c *Client) setNewsQuery(q url.Values, p GetNewsRequest) { } } +func (c *Client) setNewsPaginatedQuery(q url.Values, p GetNewsPaginatedRequest) { + c.setNewsQuery(q, p.GetNewsRequest) + if p.PageToken != "" { + q.Set("page_token", p.PageToken) + } +} + // GetNews returns the news articles based on the given req. func (c *Client) GetNews(req GetNewsRequest) ([]News, error) { + news, _, err := c.GetNewsPaginated(GetNewsPaginatedRequest{GetNewsRequest: req}) + if err != nil { + return nil, err + } + return news, nil +} + +// GetNewsPaginated returns the news articles based on the given req, and returns the nextPageToken to allow for manual pagination. +func (c *Client) GetNewsPaginated(req GetNewsPaginatedRequest) ([]News, string, error) { if req.TotalLimit < 0 { - return nil, fmt.Errorf("negative total limit") + return nil, "", fmt.Errorf("negative total limit") } if req.PageLimit < 0 { - return nil, fmt.Errorf("negative page limit") + return nil, "", fmt.Errorf("negative page limit") } if req.NoTotalLimit && req.TotalLimit != 0 { - return nil, fmt.Errorf("both NoTotalLimit and non-zero TotalLimit specified") + return nil, "", fmt.Errorf("both NoTotalLimit and non-zero TotalLimit specified") } u, err := url.Parse(fmt.Sprintf("%s/v1beta1/news", c.opts.BaseURL)) if err != nil { - return nil, fmt.Errorf("invalid news url: %w", err) + return nil, "", fmt.Errorf("invalid news url: %w", err) } q := u.Query() - c.setNewsQuery(q, req) + c.setNewsPaginatedQuery(q, req) received := 0 totalLimit := req.TotalLimit if req.TotalLimit == 0 && !req.NoTotalLimit { totalLimit = newsMaxLimit } + nextPageToken := req.PageToken news := make([]News, 0, totalLimit) for totalLimit == 0 || received < totalLimit { setQueryLimit(q, totalLimit, req.PageLimit, received, newsMaxLimit) @@ -1217,22 +1453,23 @@ func (c *Client) GetNews(req GetNewsRequest) ([]News, error) { resp, err := c.get(u) if err != nil { - return nil, fmt.Errorf("failed to get news: %w", err) + return nil, "", fmt.Errorf("failed to get news: %w", err) } var newsResp newsResponse if err = unmarshal(resp, &newsResp); err != nil { - return nil, fmt.Errorf("failed to unmarshal news: %w", err) + return nil, nextPageToken, fmt.Errorf("failed to unmarshal news: %w", err) } news = append(news, newsResp.News...) if newsResp.NextPageToken == nil { - return news, nil + return news, "", nil } + nextPageToken = *newsResp.NextPageToken q.Set("page_token", *newsResp.NextPageToken) received += len(newsResp.News) } - return news, nil + return news, nextPageToken, nil } // GetTrades returns the trades for the given symbol. diff --git a/marketdata/rest_async.go b/marketdata/rest_async.go new file mode 100644 index 0000000..9b73a7b --- /dev/null +++ b/marketdata/rest_async.go @@ -0,0 +1,183 @@ +package marketdata + +// GetTradesAsync returns the trades for the given symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetTradesAsync(symbol string, req GetTradesPaginatedRequest, callback func(trades []Trade, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetMultiTradesPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetQuotesAsync returns quotes for the given symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetQuotesAsync(symbol string, req GetQuotesPaginatedRequest, callback func(quotes []Quote, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetMultiQuotesPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetBarsAsync returns bars for the given symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetBarsAsync(symbol string, req GetBarsPaginatedRequest, callback func(bars []Bar, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetMultiBarsPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetAuctionsAsync returns auctions for the given symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetAuctionsAsync(symbol string, req GetAuctionsPaginatedRequest, callback func(auctions []DailyAuctions, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetMultiAuctionsPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetCryptoTradesAsync returns trades for the given crypto symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetCryptoTradesAsync(symbol string, req GetCryptoTradesPaginatedRequest, callback func(trades []CryptoTrade, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetCryptoMultiTradesPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetCryptoBarsAsync returns bars for the given crypto symbol asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetCryptoBarsAsync(symbol string, req GetCryptoBarsPaginatedRequest, callback func(bars []CryptoBar, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetCryptoMultiBarsPaginated([]string{symbol}, req) + keepGoing := callback(resp[symbol], err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +} + +// GetNewsAsync returns the news articles based on the given req asynchronously, triggering the callback function on each received batch. +// The callback receives the batch, and an error (if there was one). It can return a boolean to decide whether to continue streaming the data or not. +func (c *Client) GetNewsAsync(req GetNewsPaginatedRequest, callback func(news []News, err error) (keepGoing bool)) error { + if req.TotalLimit == 0 && req.PageLimit > 0 { + req.TotalLimit = req.PageLimit + } + if req.TotalLimit == 0 { + req.TotalLimit = v2MaxLimit + } + + for { + resp, nextPageToken, err := c.GetNewsPaginated(req) + keepGoing := callback(resp, err) + req.PageToken = nextPageToken + if keepGoing && nextPageToken != "" { + continue + } + if err != nil { + return err + } + if nextPageToken == "" { + return nil + } + } +}