diff --git a/README.md b/README.md index f6152f77..45bf60f9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Polygon Go Client -![Coverage](https://img.shields.io/badge/Coverage-77.4%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-74.8%25-brightgreen) diff --git a/rest/client/client.go b/rest/client/client.go index 5e5c85ad..c9101380 100644 --- a/rest/client/client.go +++ b/rest/client/client.go @@ -11,7 +11,7 @@ import ( "github.com/polygon-io/client-go/rest/models" ) -const clientVersion = "v1.16.0" +const clientVersion = "v1.16.7" const ( APIURL = "https://api.polygon.io" diff --git a/rest/example/stocks/ipos/main.go b/rest/example/stocks/ipos/main.go new file mode 100644 index 00000000..830d020f --- /dev/null +++ b/rest/example/stocks/ipos/main.go @@ -0,0 +1,33 @@ +// Stocks - Company IPOs +// https://polygon.io/docs/stocks/get_v1_reference_ipos +package main + +import ( + "context" + "log" + "os" + + polygon "github.com/polygon-io/client-go/rest" + "github.com/polygon-io/client-go/rest/models" +) + +func main() { + + // Initialize client + c := polygon.New(os.Getenv("POLYGON_API_KEY")) + + // Set parameters + params := models.ListIPOsParams{}.WithTicker("RDDT") + + // make request + iter := c.ListIPOs(context.Background(), params) + + // do something with the result + for iter.Next() { + log.Print(iter.Item()) + } + if iter.Err() != nil { + log.Fatal(iter.Err()) + } + +} diff --git a/rest/example/stocks/short-interest/main.go b/rest/example/stocks/short-interest/main.go new file mode 100644 index 00000000..082b09bf --- /dev/null +++ b/rest/example/stocks/short-interest/main.go @@ -0,0 +1,39 @@ +// Stocks - Short Interest +// https://polygon.io/docs/stocks/get_v1_reference_short-interest__identifierType___identifier +package main + +import ( + "context" + "log" + "os" + "time" + + polygon "github.com/polygon-io/client-go/rest" + "github.com/polygon-io/client-go/rest/models" +) + +func main() { + + // Initialize client + c := polygon.New(os.Getenv("POLYGON_API_KEY")) + + // Set parameters + params := models.ListShortInterestParams{ + IdentifierType: "ticker", + Identifier: "AMD", + }.WithDay(models.GTE, 2024, time.October, 1). + WithDay(models.LTE, 2024, time.October, 10). + WithLimit(10) + + // Make request + iter := c.ListShortInterest(context.Background(), params) + + // Do something with the result + for iter.Next() { + log.Print(iter.Item()) + } + if iter.Err() != nil { + log.Fatal(iter.Err()) + } + +} diff --git a/rest/models/tickers.go b/rest/models/tickers.go index d16842fa..2cc0e7ec 100644 --- a/rest/models/tickers.go +++ b/rest/models/tickers.go @@ -1,6 +1,9 @@ package models -import "strings" +import ( + "strings" + "time" +) // ListTickersParams is the set of parameters for the ListTickers method. type ListTickersParams struct { @@ -420,3 +423,206 @@ type TickerEvent struct { type TickerChangeEvent struct { Ticker string `json:"ticker"` } + +// ListShortInterestParams contains parameters for the ListShortInterest method. +type ListShortInterestParams struct { + // Path parameters + IdentifierType string `validate:"required" path:"identifierType"` + Identifier string `validate:"required" path:"identifier"` + + // Query parameters + DateEQ *Nanos `query:"date"` + DateLT *Nanos `query:"date.lt"` + DateLTE *Nanos `query:"date.lte"` + DateGT *Nanos `query:"date.gt"` + DateGTE *Nanos `query:"date.gte"` + + Order *Order `query:"order"` + Limit *int `query:"limit"` + Sort *Sort `query:"sort"` +} + +// WithDate adds date filtering to the parameters. +func (p ListShortInterestParams) WithDate(c Comparator, q Nanos) *ListShortInterestParams { + switch c { + case EQ: + p.DateEQ = &q + case LT: + p.DateLT = &q + case LTE: + p.DateLTE = &q + case GT: + p.DateGT = &q + case GTE: + p.DateGTE = &q + } + return &p +} + +// WithDay allows setting the date via year, month, day. +func (p ListShortInterestParams) WithDay(c Comparator, year int, month time.Month, day int) *ListShortInterestParams { + d := Nanos(time.Date(year, month, day, 0, 0, 0, 0, time.UTC)) + return p.WithDate(c, d) +} + +func (p ListShortInterestParams) WithOrder(order Order) *ListShortInterestParams { + p.Order = &order + return &p +} + +func (p ListShortInterestParams) WithLimit(limit int) *ListShortInterestParams { + p.Limit = &limit + return &p +} + +func (p ListShortInterestParams) WithSort(sort Sort) *ListShortInterestParams { + p.Sort = &sort + return &p +} + +// ListShortInterestResponse represents the response from the ListShortInterest method. +type ListShortInterestResponse struct { + BaseResponse + Results []ShortInterest `json:"results,omitempty"` +} + +// ShortInterest represents a single short interest data point. +type ShortInterest struct { + CurrencyCode string `json:"currency_code,omitempty"` + Date string `json:"date,omitempty"` + ISIN string `json:"isin,omitempty"` + Name string `json:"name,omitempty"` + SecurityDescription string `json:"security_description,omitempty"` + ShortVolume int64 `json:"short_volume,omitempty"` + ShortVolumeExempt int64 `json:"short_volume_exempt,omitempty"` + Ticker string `json:"ticker,omitempty"` + USCode string `json:"us_code,omitempty"` +} + +// IPOsSortField defines the sort fields for IPOs. +type IPOsSortField string + +const ( + IPOsSortListingDate IPOsSortField = "listing_date" + IPOsSortTicker IPOsSortField = "ticker" + IPOsSortLastUpdated IPOsSortField = "last_updated" + IPOsSortSecurityType IPOsSortField = "security_type" + IPOsSortIssuerName IPOsSortField = "issuer_name" + IPOsSortCurrencyCode IPOsSortField = "currency_code" + IPOsSortISIN IPOsSortField = "isin" + IPOsSortUSCode IPOsSortField = "us_code" + IPOsSortFinalIssuePrice IPOsSortField = "final_issue_price" + IPOsSortMinSharesOffered IPOsSortField = "min_shares_offered" + IPOsSortMaxSharesOffered IPOsSortField = "max_shares_offered" + IPOsSortLowestOfferPrice IPOsSortField = "lowest_offer_price" + IPOsSortHighestOfferPrice IPOsSortField = "highest_offer_price" + IPOsSortTotalOfferSize IPOsSortField = "total_offer_size" + IPOsSortSharesOutstanding IPOsSortField = "shares_outstanding" + IPOsSortPrimaryExchange IPOsSortField = "primary_exchange" + IPOsSortLotSize IPOsSortField = "lot_size" + IPOsSortSecurityDescription IPOsSortField = "security_description" + IPOsSortIPOStatus IPOsSortField = "ipo_status" +) + +// ListIPOsParams contains parameters for the ListIPOs method. +type ListIPOsParams struct { + // Query parameters + Ticker *string `query:"ticker"` + USCode *string `query:"us_code"` + ISIN *string `query:"isin"` + + ListingDateEQ *Nanos `query:"listing_date"` + ListingDateLT *Nanos `query:"listing_date.lt"` + ListingDateLTE *Nanos `query:"listing_date.lte"` + ListingDateGT *Nanos `query:"listing_date.gt"` + ListingDateGTE *Nanos `query:"listing_date.gte"` + + Order *Order `query:"order"` + Limit *int `query:"limit"` + Sort *IPOsSortField `query:"sort"` +} + +// WithListingDate adds listing date filtering to the parameters. +func (p ListIPOsParams) WithListingDate(c Comparator, q Nanos) *ListIPOsParams { + switch c { + case EQ: + p.ListingDateEQ = &q + case LT: + p.ListingDateLT = &q + case LTE: + p.ListingDateLTE = &q + case GT: + p.ListingDateGT = &q + case GTE: + p.ListingDateGTE = &q + } + return &p +} + +// WithListingDay allows setting the listing date via year, month, day. +func (p ListIPOsParams) WithListingDay(c Comparator, year int, month time.Month, day int) *ListIPOsParams { + d := Nanos(time.Date(year, month, day, 0, 0, 0, 0, time.UTC)) + return p.WithListingDate(c, d) +} + +func (p ListIPOsParams) WithTicker(ticker string) *ListIPOsParams { + p.Ticker = &ticker + return &p +} + +func (p ListIPOsParams) WithUSCode(usCode string) *ListIPOsParams { + p.USCode = &usCode + return &p +} + +func (p ListIPOsParams) WithISIN(isin string) *ListIPOsParams { + p.ISIN = &isin + return &p +} + +func (p ListIPOsParams) WithOrder(order Order) *ListIPOsParams { + p.Order = &order + return &p +} + +func (p ListIPOsParams) WithLimit(limit int) *ListIPOsParams { + p.Limit = &limit + return &p +} + +func (p ListIPOsParams) WithSort(sort IPOsSortField) *ListIPOsParams { + p.Sort = &sort + return &p +} + +// ListIPOsResponse represents the response from the ListIPOs method. +type ListIPOsResponse struct { + BaseResponse + Results []IPOListing `json:"results,omitempty"` +} + +// IPOListing represents a single IPO listing. +type IPOListing struct { + CurrencyCode string `json:"currency_code,omitempty"` + FinalIssuePrice float64 `json:"final_issue_price,omitempty"` + HighestOfferPrice float64 `json:"highest_offer_price,omitempty"` + IPOStatus string `json:"ipo_status,omitempty"` + ISIN string `json:"isin,omitempty"` + IssueEndDate string `json:"issue_end_date,omitempty"` + IssueStartDate string `json:"issue_start_date,omitempty"` + IssuerName string `json:"issuer_name,omitempty"` + LastUpdated string `json:"last_updated,omitempty"` + ListingDate string `json:"listing_date,omitempty"` + ListingPrice float64 `json:"listing_price,omitempty"` + LotSize int64 `json:"lot_size,omitempty"` + LowestOfferPrice float64 `json:"lowest_offer_price,omitempty"` + MaxSharesOffered int64 `json:"max_shares_offered,omitempty"` + MinSharesOffered int64 `json:"min_shares_offered,omitempty"` + PrimaryExchange string `json:"primary_exchange,omitempty"` + SecurityDescription string `json:"security_description,omitempty"` + SecurityType string `json:"security_type,omitempty"` + SharesOutstanding int64 `json:"shares_outstanding,omitempty"` + Ticker string `json:"ticker,omitempty"` + TotalOfferSize float64 `json:"total_offer_size,omitempty"` + USCode string `json:"us_code,omitempty"` +} diff --git a/rest/reference.go b/rest/reference.go index f7996166..993f4b94 100644 --- a/rest/reference.go +++ b/rest/reference.go @@ -10,25 +10,21 @@ import ( ) const ( - ListTickersPath = "/v3/reference/tickers" + GetExchangesPath = "/v3/reference/exchanges" + GetMarketHolidaysPath = "/v1/marketstatus/upcoming" + GetMarketStatusPath = "/v1/marketstatus/now" + GetOptionsContractPath = "/v3/reference/options/contracts/{ticker}" GetTickerDetailsPath = "/v3/reference/tickers/{ticker}" - ListTickerNewsPath = "/v2/reference/news" GetTickerRelatedCompaniesPath = "/v1/related-companies/{ticker}" GetTickerTypesPath = "/v3/reference/tickers/types" - - GetMarketHolidaysPath = "/v1/marketstatus/upcoming" - GetMarketStatusPath = "/v1/marketstatus/now" - - ListSplitsPath = "/v3/reference/splits" - - ListDividendsPath = "/v3/reference/dividends" - - ListConditionsPath = "/v3/reference/conditions" - - GetExchangesPath = "/v3/reference/exchanges" - - GetOptionsContractPath = "/v3/reference/options/contracts/{ticker}" - ListOptionsContractsPath = "/v3/reference/options/contracts" + ListConditionsPath = "/v3/reference/conditions" + ListDividendsPath = "/v3/reference/dividends" + ListIPOsPath = "/vX/reference/ipos" + ListOptionsContractsPath = "/v3/reference/options/contracts" + ListShortInterestPath = "/vX/reference/short-interest/{identifierType}/{identifier}" + ListSplitsPath = "/v3/reference/splits" + ListTickerNewsPath = "/v2/reference/news" + ListTickersPath = "/v3/reference/tickers" ) // ReferenceClient defines a REST client for the Polygon reference API. @@ -210,3 +206,23 @@ func (c *ReferenceClient) ListOptionsContracts(ctx context.Context, params *mode return res, res.Results, err }) } + +// ListShortInterest retrieves short interest data for a given identifier and date. +// For more details, see: https://polygon.io/docs/stocks/get_v1_reference_short-interest__identifiertype___identifier +func (c *ReferenceClient) ListShortInterest(ctx context.Context, params *models.ListShortInterestParams, options ...models.RequestOption) *iter.Iter[models.ShortInterest] { + return iter.NewIter(ctx, ListShortInterestPath, params, func(uri string) (iter.ListResponse, []models.ShortInterest, error) { + res := &models.ListShortInterestResponse{} + err := c.CallURL(ctx, http.MethodGet, uri, res, options...) + return res, res.Results, err + }) +} + +// ListIPOs retrieves a list of upcoming or historical IPOs. +// For more details, see: https://polygon.io/docs/stocks/get_v1_reference_ipos +func (c *ReferenceClient) ListIPOs(ctx context.Context, params *models.ListIPOsParams, options ...models.RequestOption) *iter.Iter[models.IPOListing] { + return iter.NewIter(ctx, ListIPOsPath, params, func(uri string) (iter.ListResponse, []models.IPOListing, error) { + res := &models.ListIPOsResponse{} + err := c.CallURL(ctx, http.MethodGet, uri, res, options...) + return res, res.Results, err + }) +} diff --git a/rest/reference_test.go b/rest/reference_test.go index 46dbbbae..b75448bd 100644 --- a/rest/reference_test.go +++ b/rest/reference_test.go @@ -683,3 +683,135 @@ func TestListOptionsContracts(t *testing.T) { assert.False(t, iter.Next()) assert.Nil(t, iter.Err()) } + +func TestListShortInterest(t *testing.T) { + c := polygon.New("API_KEY") + + httpmock.ActivateNonDefault(c.HTTP.GetClient()) + defer httpmock.DeactivateAndReset() + + shortInterest1 := `{ + "currency_code": "USD", + "date": "2023-12-31", + "isin": "US0378331005", + "name": "Apple Inc.", + "security_description": "Common Stock", + "short_volume": 2006566, + "short_volume_exempt": 3000, + "ticker": "AAPL", + "us_code": "378331005" + }` + + // Construct the expected API response with the short interest listing + expectedResponse := `{ + "status": "OK", + "count": 1, + "next_url": "https://api.polygon.io/vX/reference/short-interest/ticker/AAPL?cursor=nextCursorValue", + "request_id": "some-request-id", + "results": [ + ` + indent(true, shortInterest1, "\t\t") + ` + ] + }` + + // Register responders + registerResponder("https://api.polygon.io/vX/reference/short-interest/ticker/AAPL?limit=10&order=asc&sort=date", expectedResponse) + registerResponder("https://api.polygon.io/vX/reference/short-interest/ticker/AAPL?cursor=nextCursorValue", "{}") + + // Initialize parameters + params := models.ListShortInterestParams{ + IdentifierType: "ticker", + Identifier: "AAPL", + }.WithLimit(10). + WithOrder(models.Asc). + WithSort(models.Sort("date")) + + // Initialize the iterator + iter := c.ListShortInterest(context.Background(), params) + + // iter creation + assert.Nil(t, iter.Err()) + + // first item + assert.True(t, iter.Next()) + assert.Nil(t, iter.Err()) + assert.NotNil(t, iter.Item()) + var expect models.ShortInterest + err := json.Unmarshal([]byte(shortInterest1), &expect) + assert.Nil(t, err) + assert.Equal(t, expect, iter.Item()) + + // end of list + assert.False(t, iter.Next()) + assert.Nil(t, iter.Err()) +} + +func TestListIPOs(t *testing.T) { + c := polygon.New("API_KEY") + + httpmock.ActivateNonDefault(c.HTTP.GetClient()) + defer httpmock.DeactivateAndReset() + + ipo1 := `{ + "currency_code": "USD", + "final_issue_price": 17, + "highest_offer_price": 17, + "ipo_status": "HISTORY", + "isin": "US75383L1026", + "issue_end_date": "2024-06-06", + "issue_start_date": "2024-06-01", + "issuer_name": "Rapport Therapeutics Inc.", + "last_updated": "2024-06-27", + "listing_date": "2024-06-07", + "listing_price": null, + "lot_size": 100, + "lowest_offer_price": 17, + "max_shares_offered": 8000000, + "min_shares_offered": 1000000, + "primary_exchange": "XNAS", + "security_description": "Ordinary Shares", + "security_type": "CS", + "shares_outstanding": 35376457, + "ticker": "RAPP", + "total_offer_size": 136000000, + "us_code": "75383L102" + }` + + // Construct the expected API response with the IPO listing + expectedResponse := `{ + "status": "OK", + "count": 1, + "next_url": "https://api.polygon.io/vX/reference/ipos?cursor=nextCursorValue", + "request_id": "6a7e466379af0a71039d60cc78e72282", + "results": [ + ` + indent(true, ipo1, "\t\t") + ` + ] + }` + + // Register responders + registerResponder("https://api.polygon.io/vX/reference/ipos?limit=10&order=asc", expectedResponse) + registerResponder("https://api.polygon.io/vX/reference/ipos?cursor=nextCursorValue", "{}") + + // Initialize parameters + params := models.ListIPOsParams{}. + WithLimit(10). + WithOrder(models.Asc) + + // Initialize the iterator + iter := c.ListIPOs(context.Background(), params) + + // iter creation + assert.Nil(t, iter.Err()) + + // first item + assert.True(t, iter.Next()) + assert.Nil(t, iter.Err()) + assert.NotNil(t, iter.Item()) + var expect models.IPOListing + err := json.Unmarshal([]byte(ipo1), &expect) + assert.Nil(t, err) + assert.Equal(t, expect, iter.Item()) + + // end of list + assert.False(t, iter.Next()) + assert.Nil(t, iter.Err()) +}