diff --git a/client_test.go b/client_test.go index 9a071d3..eef5945 100644 --- a/client_test.go +++ b/client_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/go-resty/resty/v2" _ "github.com/joho/godotenv/autoload" ) @@ -27,6 +28,123 @@ func TestNewClient(t *testing.T) { } } +// TestGetEnvironment tests the getEnvironment method for various host URLs. +func TestGetEnvironment(t *testing.T) { + // Define test cases + tests := []struct { + name string + hostURL string + expected string + }{ + { + name: "Production Environment", + hostURL: "https://api.marketdata.app", + expected: prodEnv, + }, + { + name: "Testing Environment", + hostURL: "https://tst.marketdata.app", + expected: testEnv, + }, + { + name: "Development Environment", + hostURL: "http://localhost", + expected: devEnv, + }, + { + name: "Unknown Environment", + hostURL: "https://unknown.environment", + expected: "Unknown", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Create a new MarketDataClient instance + client := &MarketDataClient{ + Client: resty.New(), + } + + // Set the HostURL to the test case's host URL + client.Client.SetHostURL(tc.hostURL) + + // Call getEnvironment and check the result + result := client.getEnvironment() + if result != tc.expected { + t.Errorf("Expected environment %s, got %s", tc.expected, result) + } + }) + } +} + +// TestEnvironmentMethod tests the Environment method for setting the client's environment. +func TestEnvironmentMethod(t *testing.T) { + // Define test cases + tests := []struct { + name string + environment string + expectedURL string + expectError bool + }{ + { + name: "Set Production Environment", + environment: prodEnv, + expectedURL: prodProtocol + "://" + prodHost, + expectError: false, + }, + { + name: "Set Testing Environment", + environment: testEnv, + expectedURL: testProtocol + "://" + testHost, + expectError: false, + }, + { + name: "Set Development Environment", + environment: devEnv, + expectedURL: devProtocol + "://" + devHost, + expectError: false, + }, + { + name: "Set Invalid Environment", + environment: "invalidEnv", + expectedURL: "", + expectError: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Create a new MarketDataClient instance + client := NewClient() + + // Set the environment using the Environment method + client = client.Environment(tc.environment) + + // Check if an error was expected + if tc.expectError { + if client.Error == nil { + t.Errorf("Expected an error for environment %s, but got none", tc.environment) + } + } else { + if client.Error != nil { + t.Errorf("Did not expect an error for environment %s, but got: %v", tc.environment, client.Error) + } + + // Verify that the baseURL was set correctly + if client.Client.HostURL != tc.expectedURL { + t.Errorf("Expected baseURL %s, got %s", tc.expectedURL, client.Client.HostURL) + } + + // Additionally, verify that getEnvironment returns the correct environment + result := client.getEnvironment() + if result != tc.environment { + t.Errorf("Expected environment %s, got %s", tc.environment, result) + } + } + }) + } +} + /* func TestRateLimit(t *testing.T) { diff --git a/indices_candles_test.go b/indices_candles_test.go index 79b490d..cc47cd4 100644 --- a/indices_candles_test.go +++ b/indices_candles_test.go @@ -12,19 +12,19 @@ func ExampleIndicesCandlesRequest_get() { for _, candle := range vix { fmt.Println(candle) } - // Output: Time: 2022-01-03 00:00:00 -05:00, Open: 17.6, High: 18.54, Low: 16.56, Close: 16.6 - // Time: 2022-01-04 00:00:00 -05:00, Open: 16.57, High: 17.81, Low: 16.34, Close: 16.91 - // Time: 2022-01-05 00:00:00 -05:00, Open: 17.07, High: 20.17, Low: 16.58, Close: 19.73 + // Output: IndexCandle{Time: 2022-01-03 00:00:00 -05:00, Open: 17.6, High: 18.54, Low: 16.56, Close: 16.6} + // IndexCandle{Time: 2022-01-04 00:00:00 -05:00, Open: 16.57, High: 17.81, Low: 16.34, Close: 16.91} + // IndexCandle{Time: 2022-01-05 00:00:00 -05:00, Open: 17.07, High: 20.17, Low: 16.58, Close: 19.73} } func ExampleIndicesCandlesRequest_packed() { - vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2022-01-01").To("2022-01-05").Packed() + vix, err := IndexCandles().Symbol("VIX").Resolution("D").To("2022-01-05").Countback(3).Packed() if err != nil { println("Error retrieving VIX index candles:", err.Error()) return } fmt.Println(vix) - // Output: Time: [1641186000 1641272400 1641358800], Open: [17.6 16.57 17.07], High: [18.54 17.81 20.17], Low: [16.56 16.34 16.58], Close: [16.6 16.91 19.73] + // Output: IndicesCandlesResponse{Time: [1641186000 1641272400 1641358800], Open: [17.6 16.57 17.07], High: [18.54 17.81 20.17], Low: [16.56 16.34 16.58], Close: [16.6 16.91 19.73]} } func ExampleIndicesCandlesRequest_raw() { diff --git a/indices_quotes_test.go b/indices_quotes_test.go new file mode 100644 index 0000000..e905bd9 --- /dev/null +++ b/indices_quotes_test.go @@ -0,0 +1,55 @@ +package client + +import ( + "testing" +) + +func TestIndexQuoteRequest_Packed(t *testing.T) { + iqPacked, err := IndexQuotes().Symbol("VIX").FiftyTwoWeek(true).Packed() + if err != nil { + t.Errorf("Failed to get index quotes: %v", err) + return + } + + iq, err := iqPacked.Unpack() + if err != nil { + t.Errorf("Failed to unpack index quotes: %v", err) + return + } + + if len(iq) == 0 { + t.Errorf("Unpacked index quotes slice is empty") + return + } + + firstQuote := iq[0] + if firstQuote.Symbol != "VIX" { + t.Errorf("Expected symbol VIX, got %s", firstQuote.Symbol) + } + + if firstQuote.Last < 0 || firstQuote.Last > 100 { + t.Errorf("Expected last value to be between 0 and 100, got %f", firstQuote.Last) + } +} + +func TestIndexQuoteRequest_Get(t *testing.T) { + iq, err := IndexQuotes().Symbol("VIX").FiftyTwoWeek(false).Get() + if err != nil { + t.Errorf("Failed to get index quotes: %v", err) + return + } + + if len(iq) == 0 { + t.Errorf("Index quotes slice is empty") + return + } + + for _, quote := range iq { + if quote.Symbol != "VIX" { + t.Errorf("Expected symbol VIX, got %s", quote.Symbol) + } + if quote.Last < 0 || quote.Last > 100 { + t.Errorf("Expected last value to be between 0 and 100, got %f", quote.Last) + } + } +} diff --git a/markets_status_test.go b/markets_status_test.go index a7814bc..bc32eb0 100644 --- a/markets_status_test.go +++ b/markets_status_test.go @@ -1,6 +1,7 @@ package client import ( + "fmt" "testing" ) @@ -44,3 +45,38 @@ func TestMarketStatusRequestSetters(t *testing.T) { t.Errorf("Countback setter failed, got: %d, want: %d.", *msr.dateParams.Countback, countback) } } + +func ExampleMarketStatus_packed() { + + msr, err := MarketStatus().From("2022-01-01").To("2022-01-10").Packed() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(msr) + //Output: MarketStatusResponse{Date: [1641013200, 1641099600, 1641186000, 1641272400, 1641358800, 1641445200, 1641531600, 1641618000, 1641704400, 1641790800], Status: ["closed", "closed", "open", "open", "open", "open", "open", "closed", "closed", "open"]} +} + +func ExampleMarketStatus_get() { + + msr, err := MarketStatus().From("2022-01-01").To("2022-01-10").Get() + if err != nil { + fmt.Print(err) + return + } + + for _, report := range msr { + fmt.Println(report) + } + // Output: MarketStatusReport{Date: 2022-01-01 00:00:00 -0500 EST, Open: false, Closed: true} + // MarketStatusReport{Date: 2022-01-02 00:00:00 -0500 EST, Open: false, Closed: true} + // MarketStatusReport{Date: 2022-01-03 00:00:00 -0500 EST, Open: true, Closed: false} + // MarketStatusReport{Date: 2022-01-04 00:00:00 -0500 EST, Open: true, Closed: false} + // MarketStatusReport{Date: 2022-01-05 00:00:00 -0500 EST, Open: true, Closed: false} + // MarketStatusReport{Date: 2022-01-06 00:00:00 -0500 EST, Open: true, Closed: false} + // MarketStatusReport{Date: 2022-01-07 00:00:00 -0500 EST, Open: true, Closed: false} + // MarketStatusReport{Date: 2022-01-08 00:00:00 -0500 EST, Open: false, Closed: true} + // MarketStatusReport{Date: 2022-01-09 00:00:00 -0500 EST, Open: false, Closed: true} + // MarketStatusReport{Date: 2022-01-10 00:00:00 -0500 EST, Open: true, Closed: false} +} diff --git a/models/indices_candles.go b/models/indices_candles.go index 357aaac..201b312 100644 --- a/models/indices_candles.go +++ b/models/indices_candles.go @@ -23,7 +23,7 @@ type IndicesCandlesResponse struct { // Returns: // - A formatted string containing the time, open, high, low, and close values. func (icr *IndicesCandlesResponse) String() string { - return fmt.Sprintf("Time: %v, Open: %v, High: %v, Low: %v, Close: %v", + return fmt.Sprintf("IndicesCandlesResponse{Time: %v, Open: %v, High: %v, Low: %v, Close: %v}", icr.Time, icr.Open, icr.High, icr.Low, icr.Close) } @@ -96,7 +96,7 @@ type IndexCandle struct { // - A formatted string containing the time, open, high, low, and close values. func (ic IndexCandle) String() string { loc, _ := time.LoadLocation("America/New_York") - return fmt.Sprintf("Time: %s, Open: %v, High: %v, Low: %v, Close: %v", + return fmt.Sprintf("IndexCandle{Time: %s, Open: %v, High: %v, Low: %v, Close: %v}", ic.Time.In(loc).Format("2006-01-02 15:04:05 Z07:00"), ic.Open, ic.High, ic.Low, ic.Close) } diff --git a/models/indices_quotes.go b/models/indices_quotes.go index da6eb70..8a26ce8 100644 --- a/models/indices_quotes.go +++ b/models/indices_quotes.go @@ -39,19 +39,25 @@ type IndexQuote struct { // - A string that represents the IndexQuote. func (iq IndexQuote) String() string { loc, _ := time.LoadLocation("America/New_York") - if iq.High52 != nil && iq.Low52 != nil && iq.Change != nil && iq.ChangePct != nil { - return fmt.Sprintf("Symbol: %s, Last: %v, Volume: %v, Updated: %s, High52: %v, Low52: %v, Change: %v, ChangePct: %v", - iq.Symbol, iq.Last, iq.Volume, iq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *iq.High52, *iq.Low52, *iq.Change, *iq.ChangePct) - } else if iq.High52 != nil && iq.Low52 != nil { - return fmt.Sprintf("Symbol: %s, Last: %v, Volume: %v, Updated: %s, High52: %v, Low52: %v", - iq.Symbol, iq.Last, iq.Volume, iq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *iq.High52, *iq.Low52) - } else if iq.Change != nil && iq.ChangePct != nil { - return fmt.Sprintf("Symbol: %s, Last: %v, Volume: %v, Updated: %s, Change: %v, ChangePct: %v", - iq.Symbol, iq.Last, iq.Volume, iq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *iq.Change, *iq.ChangePct) - } else { - return fmt.Sprintf("Symbol: %s, Last: %v, Volume: %v, Updated: %s", - iq.Symbol, iq.Last, iq.Volume, iq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00")) + updatedFormat := iq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00") + high52 := "nil" + if iq.High52 != nil { + high52 = fmt.Sprintf("%v", *iq.High52) } + low52 := "nil" + if iq.Low52 != nil { + low52 = fmt.Sprintf("%v", *iq.Low52) + } + change := "nil" + if iq.Change != nil { + change = fmt.Sprintf("%v", *iq.Change) + } + changePct := "nil" + if iq.ChangePct != nil { + changePct = fmt.Sprintf("%v", *iq.ChangePct) + } + return fmt.Sprintf("IndexQuote{Symbol: %q, Last: %v, Volume: %v, Updated: %q, High52: %s, Low52: %s, Change: %s, ChangePct: %s}", + iq.Symbol, iq.Last, iq.Volume, updatedFormat, high52, low52, change, changePct) } // Unpack transforms the IndexQuotesResponse into a slice of IndexQuote. @@ -93,22 +99,22 @@ func (iqr *IndexQuotesResponse) Unpack() ([]IndexQuote, error) { func (iqr *IndexQuotesResponse) String() string { var result strings.Builder - fmt.Fprintf(&result, "Symbol: [%v], Last: [%v]", strings.Join(iqr.Symbol, ", "), joinFloat64Slice(iqr.Last)) + result.WriteString(fmt.Sprintf("IndexQuotesResponse{Symbol: [%v], Last: [%v]", strings.Join(iqr.Symbol, ", "), joinFloat64Slice(iqr.Last))) if iqr.Change != nil { - fmt.Fprintf(&result, ", Change: [%v]", joinFloat64PointerSlice(iqr.Change)) + result.WriteString(fmt.Sprintf(", Change: [%v]", joinFloat64PointerSlice(iqr.Change))) } if iqr.ChangePct != nil { - fmt.Fprintf(&result, ", ChangePct: [%v]", joinFloat64PointerSlice(iqr.ChangePct)) + result.WriteString(fmt.Sprintf(", ChangePct: [%v]", joinFloat64PointerSlice(iqr.ChangePct))) } if iqr.High52 != nil { - fmt.Fprintf(&result, ", High52: [%v]", joinFloat64Slice(*iqr.High52)) + result.WriteString(fmt.Sprintf(", High52: [%v]", joinFloat64Slice(*iqr.High52))) } if iqr.Low52 != nil { - fmt.Fprintf(&result, ", Low52: [%v]", joinFloat64Slice(*iqr.Low52)) + result.WriteString(fmt.Sprintf(", Low52: [%v]", joinFloat64Slice(*iqr.Low52))) } - fmt.Fprintf(&result, ", Updated: [%v]", joinInt64Slice(iqr.Updated)) + result.WriteString(fmt.Sprintf(", Updated: [%v]}", joinInt64Slice(iqr.Updated))) return result.String() } diff --git a/models/markets_status.go b/models/markets_status.go index fe8f991..af4127a 100644 --- a/models/markets_status.go +++ b/models/markets_status.go @@ -28,17 +28,19 @@ func (msr *MarketStatusResponse) IsValid() bool { // Returns: // - string: A string representation of the MarketStatusResponse. func (msr *MarketStatusResponse) String() string { - var parts []string - if msr.Status != nil && len(msr.Date) == len(*msr.Status) { - for i, date := range msr.Date { - t := time.Unix(date, 0) - dateStr := t.Format("2006-01-02") - status := (*msr.Status)[i] - part := fmt.Sprintf("Date: %s, Status: %s", dateStr, status) - parts = append(parts, part) - } - } - return "MarketStatusResponse{\n" + strings.Join(parts, ",\n") + "\n}" + dateParts := make([]string, len(msr.Date)) + for i, date := range msr.Date { + dateParts[i] = fmt.Sprintf("%v", date) + } + statusParts := "nil" + if msr.Status != nil { + statusSlice := make([]string, len(*msr.Status)) + for i, status := range *msr.Status { + statusSlice[i] = fmt.Sprintf("%q", status) + } + statusParts = "[" + strings.Join(statusSlice, ", ") + "]" + } + return fmt.Sprintf("MarketStatusResponse{Date: [%s], Status: %s}", strings.Join(dateParts, ", "), statusParts) } // String returns a string representation of the MarketStatusReport. @@ -46,7 +48,7 @@ func (msr *MarketStatusResponse) String() string { // Returns: // - string: A string representation of the MarketStatusReport. func (ms MarketStatusReport) String() string { - return fmt.Sprintf("MarketStatus{Date: %v, Open: %v, Closed: %v}", ms.Date, ms.Open, ms.Closed) + return fmt.Sprintf("MarketStatusReport{Date: %v, Open: %v, Closed: %v}", ms.Date, ms.Open, ms.Closed) } // Unpack unpacks the MarketStatusResponse into a slice of MarketStatusReport. diff --git a/models/options_expirations.go b/models/options_expirations.go index 7c34be3..b26d7ec 100644 --- a/models/options_expirations.go +++ b/models/options_expirations.go @@ -42,7 +42,7 @@ func (oer *OptionsExpirationsResponse) IsValid() bool { // Returns: // - A string that represents the OptionsExpirationsResponse object. func (oer *OptionsExpirationsResponse) String() string { - return fmt.Sprintf("Expirations: %v, Updated: %v", oer.Expirations, oer.Updated) + return fmt.Sprintf("OptionsExpirationsResponse{Expirations: %v, Updated: %d}", oer.Expirations, oer.Updated) } // Unpack converts the expiration date strings in the OptionsExpirationsResponse to a slice of time.Time objects. diff --git a/models/options_lookup.go b/models/options_lookup.go index 05564b7..ac7c542 100644 --- a/models/options_lookup.go +++ b/models/options_lookup.go @@ -15,10 +15,7 @@ type OptionLookupResponse struct { // Returns: // - bool: True if the OptionLookupResponse has a non-empty OptionSymbol, otherwise false. func (olr *OptionLookupResponse) IsValid() bool { - if olr.OptionSymbol == "" { - return false - } - return true + return olr.OptionSymbol != "" } // String returns a string representation of the OptionLookupResponse. @@ -26,7 +23,7 @@ func (olr *OptionLookupResponse) IsValid() bool { // Returns: // - string: A string that represents the OptionLookupResponse, including the OptionSymbol. func (olr *OptionLookupResponse) String() string { - return fmt.Sprintf("OptionSymbol: %v", olr.OptionSymbol) + return fmt.Sprintf("OptionLookupResponse{OptionSymbol: %q}", olr.OptionSymbol) } // Unpack validates the OptionLookupResponse and returns the OptionSymbol if valid. diff --git a/models/options_quotes.go b/models/options_quotes.go index 55a3616..04d77a9 100644 --- a/models/options_quotes.go +++ b/models/options_quotes.go @@ -2,7 +2,6 @@ package models import ( "fmt" - "strings" "time" ) @@ -13,32 +12,32 @@ import ( // OptionQuotesResponse represents the JSON structure of the response received for option quotes. // It includes slices for various option attributes such as symbols, underlying assets, expiration times, and pricing information. type OptionQuotesResponse struct { - OptionSymbol []string `json:"optionSymbol"` // OptionSymbol holds the symbols of the options. - Underlying []string `json:"underlying"` // Underlying contains the symbols of the underlying assets. - Expiration []int64 `json:"expiration"` // Expiration stores UNIX timestamps for when the options expire. - Side []string `json:"side"` // Side indicates whether the option is a call or a put. - Strike []float64 `json:"strike"` // Strike represents the strike prices of the options. - FirstTraded []int64 `json:"firstTraded"` // FirstTraded stores UNIX timestamps for when the options were first traded. - DTE []int `json:"dte"` // DTE (Days to Expiration) indicates the number of days until the options expire. - Ask []float64 `json:"ask"` // Ask contains the ask prices of the options. - AskSize []int64 `json:"askSize"` // AskSize holds the sizes of the ask orders. - Bid []float64 `json:"bid"` // Bid contains the bid prices of the options. - BidSize []int64 `json:"bidSize"` // BidSize holds the sizes of the bid orders. - Mid []float64 `json:"mid"` // Mid represents the mid prices calculated between the bid and ask prices. - Last []float64 `json:"last"` // Last contains the last traded prices of the options. - Volume []int64 `json:"volume"` // Volume indicates the trading volumes of the options. - OpenInterest []int64 `json:"openInterest"` // OpenInterest represents the total number of outstanding contracts. - UnderlyingPrice []float64 `json:"underlyingPrice"` // UnderlyingPrice contains the prices of the underlying assets. - InTheMoney []bool `json:"inTheMoney"` // InTheMoney indicates whether the options are in the money. - Updated []int64 `json:"updated"` // Updated stores UNIX timestamps for when the option data was last updated. - IV []float64 `json:"iv"` // IV (Implied Volatility) represents the implied volatilities of the options. - Delta []float64 `json:"delta"` // Delta measures the rate of change of the option's price with respect to the underlying asset's price. - Gamma []float64 `json:"gamma"` // Gamma measures the rate of change in delta over the underlying asset's price movement. - Theta []float64 `json:"theta"` // Theta represents the rate of decline in the option's value with time. - Vega []float64 `json:"vega"` // Vega measures sensitivity to volatility. - Rho []float64 `json:"rho"` // Rho measures sensitivity to the interest rate. - IntrinsicValue []float64 `json:"intrinsicValue"` // IntrinsicValue represents the value of the option if it were exercised now. - ExtrinsicValue []float64 `json:"extrinsicValue"` // ExtrinsicValue represents the value of the option above its intrinsic value. + OptionSymbol []string `json:"optionSymbol"` // OptionSymbol holds the symbols of the options. + Underlying []string `json:"underlying"` // Underlying contains the symbols of the underlying assets. + Expiration []int64 `json:"expiration"` // Expiration stores UNIX timestamps for when the options expire. + Side []string `json:"side"` // Side indicates whether the option is a call or a put. + Strike []float64 `json:"strike"` // Strike represents the strike prices of the options. + FirstTraded []int64 `json:"firstTraded"` // FirstTraded stores UNIX timestamps for when the options were first traded. + DTE []int `json:"dte"` // DTE (Days to Expiration) indicates the number of days until the options expire. + Ask []float64 `json:"ask"` // Ask contains the ask prices of the options. + AskSize []int64 `json:"askSize"` // AskSize holds the sizes of the ask orders. + Bid []float64 `json:"bid"` // Bid contains the bid prices of the options. + BidSize []int64 `json:"bidSize"` // BidSize holds the sizes of the bid orders. + Mid []float64 `json:"mid"` // Mid represents the mid prices calculated between the bid and ask prices. + Last []float64 `json:"last"` // Last contains the last traded prices of the options. + Volume []int64 `json:"volume"` // Volume indicates the trading volumes of the options. + OpenInterest []int64 `json:"openInterest"` // OpenInterest represents the total number of outstanding contracts. + UnderlyingPrice []float64 `json:"underlyingPrice"` // UnderlyingPrice contains the prices of the underlying assets. + InTheMoney []bool `json:"inTheMoney"` // InTheMoney indicates whether the options are in the money. + Updated []int64 `json:"updated"` // Updated stores UNIX timestamps for when the option data was last updated. + IV []*float64 `json:"iv,omitempty"` // IV (Implied Volatility) represents the implied volatilities of the options. + Delta []*float64 `json:"delta,omitempty"` // Delta measures the rate of change of the option's price with respect to the underlying asset's price. + Gamma []*float64 `json:"gamm,omitempty"` // Gamma measures the rate of change in delta over the underlying asset's price movement. + Theta []*float64 `json:"theta,omitempty"` // Theta represents the rate of decline in the option's value with time. + Vega []*float64 `json:"vega,omitempty"` // Vega measures sensitivity to volatility. + Rho []*float64 `json:"rho,omitempty"` // Rho measures sensitivity to the interest rate. + IntrinsicValue []float64 `json:"intrinsicValue"` // IntrinsicValue represents the value of the option if it were exercised now. + ExtrinsicValue []float64 `json:"extrinsicValue"` // ExtrinsicValue represents the value of the option above its intrinsic value. } // OptionQuote represents a single option quote with detailed information such as the symbol, underlying asset, expiration time, and pricing information. @@ -61,16 +60,58 @@ type OptionQuote struct { UnderlyingPrice float64 // UnderlyingPrice is the price of the underlying asset. InTheMoney bool // InTheMoney indicates whether the option is in the money. Updated time.Time // Updated is the time when the option data was last updated. - IV float64 // IV (Implied Volatility) is the implied volatility of the option. - Delta float64 // Delta measures the rate of change of the option's price with respect to the underlying asset's price. - Gamma float64 // Gamma measures the rate of change in delta over the underlying asset's price movement. - Theta float64 // Theta represents the rate of decline in the option's value with time. - Vega float64 // Vega measures sensitivity to volatility. - Rho float64 // Rho measures sensitivity to the interest rate. + IV *float64 // IV (Implied Volatility) is the implied volatility of the option. + Delta *float64 // Delta measures the rate of change of the option's price with respect to the underlying asset's price. + Gamma *float64 // Gamma measures the rate of change in delta over the underlying asset's price movement. + Theta *float64 // Theta represents the rate of decline in the option's value with time. + Vega *float64 // Vega measures sensitivity to volatility. + Rho *float64 // Rho measures sensitivity to the interest rate. IntrinsicValue float64 // IntrinsicValue is the value of the option if it were exercised now. ExtrinsicValue float64 // ExtrinsicValue is the value of the option above its intrinsic value. } +func (oq OptionQuote) DisplayIV() string { + if oq.IV != nil { + return fmt.Sprintf("%f", *oq.IV) + } + return "nil" +} + +func (oq OptionQuote) DisplayDelta() string { + if oq.Delta != nil { + return fmt.Sprintf("%f", *oq.Delta) + } + return "nil" +} + +func (oq OptionQuote) DisplayGamma() string { + if oq.Gamma != nil { + return fmt.Sprintf("%f", *oq.Gamma) + } + return "nil" +} + +func (oq OptionQuote) DisplayTheta() string { + if oq.Theta != nil { + return fmt.Sprintf("%f", *oq.Theta) + } + return "nil" +} + +func (oq OptionQuote) DisplayVega() string { + if oq.Vega != nil { + return fmt.Sprintf("%f", *oq.Vega) + } + return "nil" +} + +func (oq OptionQuote) DisplayRho() string { + if oq.Rho != nil { + return fmt.Sprintf("%f", *oq.Rho) + } + return "nil" +} + // String returns a formatted string representation of an OptionQuote. // // Parameters: @@ -80,8 +121,70 @@ type OptionQuote struct { // - A string that represents the OptionQuote in a human-readable format. func (oq OptionQuote) String() string { loc, _ := time.LoadLocation("America/New_York") - return fmt.Sprintf("Option Symbol: %s, Underlying: %s, Expiration: %v, Side: %s, Strike: %v, First Traded: %v, DTE: %v, Ask: %v, Ask Size: %v, Bid: %v, Bid Size: %v, Mid: %v, Last: %v, Volume: %v, Open Interest: %v, Underlying Price: %v, In The Money: %v, Updated: %s, IV: %v, Delta: %v, Gamma: %v, Theta: %v, Vega: %v, Rho: %v, Intrinsic Value: %v, Extrinsic Value: %v", - oq.OptionSymbol, oq.Underlying, oq.Expiration, oq.Side, oq.Strike, oq.FirstTraded, oq.DTE, oq.Ask, oq.AskSize, oq.Bid, oq.BidSize, oq.Mid, oq.Last, oq.Volume, oq.OpenInterest, oq.UnderlyingPrice, oq.InTheMoney, oq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), oq.IV, oq.Delta, oq.Gamma, oq.Theta, oq.Vega, oq.Rho, oq.IntrinsicValue, oq.ExtrinsicValue) + return fmt.Sprintf("OptionQuote{OptionSymbol: %q, Underlying: %q, Expiration: %v, Side: %q, Strike: %v, FirstTraded: %v, DTE: %v, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, OpenInterest: %v, UnderlyingPrice: %v, InTheMoney: %v, Updated: %q, IV: %s, Delta: %s, Gamma: %s, Theta: %s, Vega: %s, Rho: %s, IntrinsicValue: %v, ExtrinsicValue: %v}", + oq.OptionSymbol, oq.Underlying, oq.Expiration.In(loc).Format("2006-01-02 15:04:05 Z07:00"), oq.Side, oq.Strike, oq.FirstTraded.In(loc).Format("2006-01-02 15:04:05 Z07:00"), oq.DTE, oq.Ask, oq.AskSize, oq.Bid, oq.BidSize, oq.Mid, oq.Last, oq.Volume, oq.OpenInterest, oq.UnderlyingPrice, oq.InTheMoney, oq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), oq.DisplayIV(), oq.DisplayDelta(), oq.DisplayGamma(), oq.DisplayTheta(), oq.DisplayVega(), oq.DisplayRho(), oq.IntrinsicValue, oq.ExtrinsicValue) +} + +// IsValid checks if an OptionQuotesResponse is valid by utilizing the Validate method. +// +// Returns: +// - A boolean indicating if the OptionQuotesResponse is valid. +func (oqr *OptionQuotesResponse) IsValid() bool { + if err := oqr.Validate(); err != nil { + return false + } + return true +} + +// Validate checks the consistency of the OptionQuotesResponse struct. +// +// Returns: +// - An error if there is an inconsistency in the lengths of slices or if there are no option symbols. +func (oqr *OptionQuotesResponse) Validate() error { + if len(oqr.OptionSymbol) == 0 { + return fmt.Errorf("no option symbols found in the response") + } + expectedLength := len(oqr.OptionSymbol) + slices := map[string]int{ + "Underlying": len(oqr.Underlying), + "Expiration": len(oqr.Expiration), + "Side": len(oqr.Side), + "Strike": len(oqr.Strike), + "FirstTraded": len(oqr.FirstTraded), + "DTE": len(oqr.DTE), + "Ask": len(oqr.Ask), + "AskSize": len(oqr.AskSize), + "Bid": len(oqr.Bid), + "BidSize": len(oqr.BidSize), + "Mid": len(oqr.Mid), + "Last": len(oqr.Last), + "Volume": len(oqr.Volume), + "OpenInterest": len(oqr.OpenInterest), + "UnderlyingPrice": len(oqr.UnderlyingPrice), + "InTheMoney": len(oqr.InTheMoney), + "Updated": len(oqr.Updated), + "IV": len(oqr.IV), + "Delta": len(oqr.Delta), + "Gamma": len(oqr.Gamma), + "Theta": len(oqr.Theta), + "Vega": len(oqr.Vega), + "Rho": len(oqr.Rho), + "IntrinsicValue": len(oqr.IntrinsicValue), + "ExtrinsicValue": len(oqr.ExtrinsicValue), + } + + for sliceName, length := range slices { + if sliceName == "IV" || sliceName == "Delta" || sliceName == "Gamma" || sliceName == "Theta" || sliceName == "Vega" || sliceName == "Rho" { + if length != 0 && length != expectedLength { + return fmt.Errorf("inconsistent length for slice %q: expected %d or 0, got %d", sliceName, expectedLength, length) + } + } else { + if length != expectedLength { + return fmt.Errorf("inconsistent length for slice %q: expected %d, got %d", sliceName, expectedLength, length) + } + } + } + return nil } // Unpack converts the OptionQuotesResponse into a slice of OptionQuote structs. @@ -91,45 +194,51 @@ func (oq OptionQuote) String() string { // // Returns: // - A slice of OptionQuote structs that represent the unpacked data. -// - An error if the time zone loading fails. +// - An error if the time zone loading fails or if the validation fails. func (oqr *OptionQuotesResponse) Unpack() ([]OptionQuote, error) { - loc, err := time.LoadLocation("America/New_York") // Load the New York time zone - if err != nil { - return nil, fmt.Errorf("failed to load New York time zone: %v", err) - } - var optionQuotes []OptionQuote - for i := range oqr.OptionSymbol { - optionQuote := OptionQuote{ - OptionSymbol: oqr.OptionSymbol[i], - Underlying: oqr.Underlying[i], - Expiration: time.Unix(oqr.Expiration[i], 0).In(loc), // Convert to time.Time in America/New_York - Side: oqr.Side[i], - Strike: oqr.Strike[i], - FirstTraded: time.Unix(oqr.FirstTraded[i], 0).In(loc), // Convert to time.Time in America/New_York - DTE: oqr.DTE[i], - Ask: oqr.Ask[i], - AskSize: oqr.AskSize[i], - Bid: oqr.Bid[i], - BidSize: oqr.BidSize[i], - Mid: oqr.Mid[i], - Last: oqr.Last[i], - Volume: oqr.Volume[i], - OpenInterest: oqr.OpenInterest[i], - UnderlyingPrice: oqr.UnderlyingPrice[i], - InTheMoney: oqr.InTheMoney[i], - Updated: time.Unix(oqr.Updated[i], 0).In(loc), // Convert to time.Time in America/New_York - IV: oqr.IV[i], - Delta: oqr.Delta[i], - Gamma: oqr.Gamma[i], - Theta: oqr.Theta[i], - Vega: oqr.Vega[i], - Rho: oqr.Rho[i], - IntrinsicValue: oqr.IntrinsicValue[i], - ExtrinsicValue: oqr.ExtrinsicValue[i], - } - optionQuotes = append(optionQuotes, optionQuote) - } - return optionQuotes, nil + // Validate the OptionQuotesResponse before unpacking. + if err := oqr.Validate(); err != nil { + return nil, err + } + + loc, err := time.LoadLocation("America/New_York") // Load the New York time zone + if err != nil { + return nil, fmt.Errorf("failed to load New York time zone: %v", err) + } + + var optionQuotes []OptionQuote + for i := range oqr.OptionSymbol { + optionQuote := OptionQuote{ + OptionSymbol: oqr.OptionSymbol[i], + Underlying: oqr.Underlying[i], + Expiration: time.Unix(oqr.Expiration[i], 0).In(loc), + Side: oqr.Side[i], + Strike: oqr.Strike[i], + FirstTraded: time.Unix(oqr.FirstTraded[i], 0).In(loc), + DTE: oqr.DTE[i], + Ask: oqr.Ask[i], + AskSize: oqr.AskSize[i], + Bid: oqr.Bid[i], + BidSize: oqr.BidSize[i], + Mid: oqr.Mid[i], + Last: oqr.Last[i], + Volume: oqr.Volume[i], + OpenInterest: oqr.OpenInterest[i], + UnderlyingPrice: oqr.UnderlyingPrice[i], + InTheMoney: oqr.InTheMoney[i], + Updated: time.Unix(oqr.Updated[i], 0).In(loc), + IV: nilIfEmpty(oqr.IV, i), + Delta: nilIfEmpty(oqr.Delta, i), + Gamma: nilIfEmpty(oqr.Gamma, i), + Theta: nilIfEmpty(oqr.Theta, i), + Vega: nilIfEmpty(oqr.Vega, i), + Rho: nilIfEmpty(oqr.Rho, i), + IntrinsicValue: oqr.IntrinsicValue[i], + ExtrinsicValue: oqr.ExtrinsicValue[i], + } + optionQuotes = append(optionQuotes, optionQuote) + } + return optionQuotes, nil } // String returns a formatted string representation of all OptionQuotes contained in the OptionQuotesResponse. @@ -139,17 +248,37 @@ func (oqr *OptionQuotesResponse) Unpack() ([]OptionQuote, error) { // // Returns: // - A string that represents all OptionQuotes in a human-readable format. -// - If an error occurs during unpacking, it returns a string describing the error. func (oqr *OptionQuotesResponse) String() string { - optionQuotes, err := oqr.Unpack() - if err != nil { - return fmt.Sprintf("Error unpacking OptionQuotesResponse: %v", err) - } + // Convert slices of pointers to strings using the helper function. + ivStr := formatFloat64Slice(oqr.IV) + deltaStr := formatFloat64Slice(oqr.Delta) + gammaStr := formatFloat64Slice(oqr.Gamma) + thetaStr := formatFloat64Slice(oqr.Theta) + vegaStr := formatFloat64Slice(oqr.Vega) + rhoStr := formatFloat64Slice(oqr.Rho) - var quotesStrings []string - for _, quote := range optionQuotes { - quotesStrings = append(quotesStrings, quote.String()) - } + return fmt.Sprintf("OptionQuotesResponse{OptionSymbol: %q, Underlying: %q, Expiration: %v, Side: %q, Strike: %v, FirstTraded: %v, DTE: %v, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, OpenInterest: %v, UnderlyingPrice: %v, InTheMoney: %t, Updated: %v, IV: %s, Delta: %s, Gamma: %s, Theta: %s, Vega: %s, Rho: %s, IntrinsicValue: %v, ExtrinsicValue: %v}", + oqr.OptionSymbol, oqr.Underlying, oqr.Expiration, oqr.Side, oqr.Strike, oqr.FirstTraded, oqr.DTE, oqr.Ask, oqr.AskSize, oqr.Bid, oqr.BidSize, oqr.Mid, oqr.Last, oqr.Volume, oqr.OpenInterest, oqr.UnderlyingPrice, oqr.InTheMoney, oqr.Updated, ivStr, deltaStr, gammaStr, thetaStr, vegaStr, rhoStr, oqr.IntrinsicValue, oqr.ExtrinsicValue) +} - return strings.Join(quotesStrings, "\n") +// formatFloat64Slice is a helper function to format slices of *float64 for printing. +func formatFloat64Slice(slice []*float64) string { + var result []string + for _, ptr := range slice { + if ptr != nil { + result = append(result, fmt.Sprintf("%f", *ptr)) + } else { + result = append(result, "nil") + } + } + return fmt.Sprintf("%v", result) } + +// nilIfEmpty checks if the slice is nil or empty and returns nil for the current index if so. +// This is a helper function to handle nil slices for pointer fields. +func nilIfEmpty(slice []*float64, index int) *float64 { + if len(slice) == 0 { + return nil + } + return slice[index] +} \ No newline at end of file diff --git a/models/options_strikes.go b/models/options_strikes.go index da5ba5a..d9652de 100644 --- a/models/options_strikes.go +++ b/models/options_strikes.go @@ -3,8 +3,11 @@ package models import ( "encoding/json" "fmt" + "sort" "strings" "time" + + "github.com/iancoleman/orderedmap" ) /* @@ -29,8 +32,8 @@ type OptionsStrikes struct { // OptionsStrikesResponse encapsulates the response structure for a request to retrieve option strikes. type OptionsStrikesResponse struct { - Updated int `json:"updated"` // Updated is a UNIX timestamp indicating when the data was last updated. - Strikes map[string][]float64 `json:"-"` // Strikes is a map where each key is a date string and the value is a slice of strike prices for that date. + Updated int `json:"updated"` // Updated is a UNIX timestamp indicating when the data was last updated. + Strikes *orderedmap.OrderedMap `json:"-"` // Strikes is a map where each key is a date string and the value is a slice of strike prices for that date. } // UnmarshalJSON custom unmarshals the JSON data into the OptionsStrikesResponse struct. @@ -40,25 +43,50 @@ type OptionsStrikesResponse struct { // // Returns: // - error: An error if unmarshaling fails, nil otherwise. +// +// UnmarshalJSON custom unmarshals the JSON data into the OptionsStrikesResponse struct. func (osr *OptionsStrikesResponse) UnmarshalJSON(data []byte) error { var aux map[string]interface{} if err := json.Unmarshal(data, &aux); err != nil { return err } - osr.Strikes = make(map[string][]float64) - for key, value := range aux { + // Initialize the OrderedMap + osr.Strikes = orderedmap.New() + + // Extract and sort the keys (excluding "updated" and "s") + var keys []string + for key := range aux { if key != "updated" && key != "s" { - values := value.([]interface{}) - floats := make([]float64, len(values)) - for i, v := range values { - floats[i] = v.(float64) + keys = append(keys, key) + } + } + sort.Strings(keys) // Sorts the keys alphabetically, which works well for ISO 8601 dates + + // Iterate over the sorted keys and add them to the OrderedMap + for _, key := range keys { + values, ok := aux[key].([]interface{}) + if !ok { + return fmt.Errorf("unexpected type for key %s", key) + } + floats := make([]float64, len(values)) + for i, v := range values { + floatVal, ok := v.(float64) + if !ok { + return fmt.Errorf("unexpected type for value in key %s", key) } - osr.Strikes[key] = floats - } else if key == "updated" { - osr.Updated = int(value.(float64)) + floats[i] = floatVal } + osr.Strikes.Set(key, floats) + } + + // Handle the "updated" key separately + if updated, ok := aux["updated"].(float64); ok { + osr.Updated = int(updated) + } else { + return fmt.Errorf("unexpected type or missing 'updated' key") } + return nil } @@ -68,30 +96,36 @@ func (osr *OptionsStrikesResponse) UnmarshalJSON(data []byte) error { // - string: The string representation of the OptionsStrikes. func (os OptionsStrikes) String() string { loc, _ := time.LoadLocation("America/New_York") - dateStr := os.Expiration.In(loc).Format("Jan 02, 2006 15:04 MST") + dateStr := os.Expiration.In(loc).Format("2006-01-02") - var strikesStrBuilder strings.Builder - strikesStrBuilder.WriteString("[") - for i, strike := range os.Strikes { - if i > 0 { - strikesStrBuilder.WriteString(", ") - } - strikesStrBuilder.WriteString(fmt.Sprintf("%.2f", strike)) + // Convert each strike price to a string with two decimal places + var strikesStrParts []string + for _, strike := range os.Strikes { + strikesStrParts = append(strikesStrParts, fmt.Sprintf("%.2f", strike)) } - strikesStrBuilder.WriteString("]") + // Join the formatted strike prices with a space + strikesStr := strings.Join(strikesStrParts, " ") - return fmt.Sprintf("Expiration: %s, Strikes: %s", dateStr, strikesStrBuilder.String()) + return fmt.Sprintf("OptionsStrikes{Expiration: %s, Strikes: [%s]}", dateStr, strikesStr) } -// IsValid checks if the OptionsStrikesResponse is valid. +// IsValid checks if the OptionsStrikesResponse is valid by leveraging the Validate method. // // Returns: // - bool: True if the response is valid, false otherwise. func (osr *OptionsStrikesResponse) IsValid() bool { - if len(osr.Strikes) == 0 { - return false + return osr.Validate() == nil +} + +// Validate checks if the OptionsStrikesResponse is valid. +// +// Returns: +// - error: An error if the response is not valid, nil otherwise. +func (osr *OptionsStrikesResponse) Validate() error { + if len(osr.Strikes.Keys()) == 0 { + return fmt.Errorf("invalid OptionsStrikesResponse: no strikes data") } - return true + return nil } // String returns a string representation of the OptionsStrikesResponse struct. @@ -99,36 +133,34 @@ func (osr *OptionsStrikesResponse) IsValid() bool { // Returns: // - string: The string representation of the OptionsStrikesResponse. func (osr *OptionsStrikesResponse) String() string { - // First, unpack the response to get a slice of OptionsStrikes - unpackedStrikes, err := osr.Unpack() - if err != nil { - return fmt.Sprintf("Error unpacking strikes: %v", err) - } - - // Initialize a builder for constructing the output string var sb strings.Builder + sb.WriteString("OptionsStrikesResponse{Strikes: [") + first := true + for _, key := range osr.Strikes.Keys() { + if !first { + sb.WriteString(", ") + } + first = false + value, _ := osr.Strikes.Get(key) + strikes, _ := value.([]float64) // Assuming the type assertion is always successful. + + // Convert strike prices to strings and join them + var strikeStrs []string + for _, strike := range strikes { + strikeStrs = append(strikeStrs, fmt.Sprintf("%.2f", strike)) + } + strikesStr := strings.Join(strikeStrs, " ") - // Loop over each OptionsStrikes in the unpacked slice - for _, strike := range unpackedStrikes { - // Use the String method of OptionsStrikes to append each to the builder - sb.WriteString(strike.String() + "; ") + sb.WriteString(fmt.Sprintf("%s:[%s]", key, strikesStr)) } - - // Append the "Updated" information last - sb.WriteString(fmt.Sprintf("Updated: %v", osr.Updated)) - - // Return the constructed string + sb.WriteString(fmt.Sprintf("], Updated: %d}", osr.Updated)) return sb.String() } -// Unpack converts the map of strikes in the response to a slice of OptionsStrikes. -// -// Returns: -// - []OptionsStrikes: A slice of OptionsStrikes constructed from the response. -// - error: An error if the unpacking fails, nil otherwise. +// Unpack converts the ordered map of strikes in the response to a slice of OptionsStrikes. func (osr *OptionsStrikesResponse) Unpack() ([]OptionsStrikes, error) { - if !osr.IsValid() { - return nil, fmt.Errorf("invalid OptionsStrikesResponse") + if err := osr.Validate(); err != nil { + return nil, err } loc, err := time.LoadLocation("America/New_York") @@ -137,13 +169,14 @@ func (osr *OptionsStrikesResponse) Unpack() ([]OptionsStrikes, error) { } var unpackedStrikes []OptionsStrikes - for dateStr, strikes := range osr.Strikes { - // Parse the date in the given location - date, err := time.ParseInLocation("2006-01-02", dateStr, loc) + for _, key := range osr.Strikes.Keys() { + value, _ := osr.Strikes.Get(key) + strikes := value.([]float64) + + date, err := time.ParseInLocation("2006-01-02", key, loc) if err != nil { - return nil, fmt.Errorf("error parsing date %s: %v", dateStr, err) + return nil, fmt.Errorf("error parsing date %s: %v", key, err) } - // Set the time to 16:00:00 date = time.Date(date.Year(), date.Month(), date.Day(), 16, 0, 0, 0, loc) unpackedStrikes = append(unpackedStrikes, OptionsStrikes{ diff --git a/models/stocks_candles.go b/models/stocks_candles.go index 2eaab09..0cbfff2 100644 --- a/models/stocks_candles.go +++ b/models/stocks_candles.go @@ -91,19 +91,20 @@ func (s *StockCandlesResponse) String() string { // Determine the version of the struct version, _ := s.getVersion() + vwap := "nil" + n := "nil" + if s.VWAP != nil { + vwap = fmt.Sprint(*s.VWAP) + } + if s.N != nil { + n = fmt.Sprint(*s.N) + } + if version == 1 { - return fmt.Sprintf("Time: %v, Open: %v, High: %v, Low: %v, Close: %v, Volume: %v", + return fmt.Sprintf("StockCandlesResponse{Time: %v, Open: %v, High: %v, Low: %v, Close: %v, Volume: %v}", s.Time, s.Open, s.High, s.Low, s.Close, s.Volume) } else { - vwap := "nil" - n := "nil" - if s.VWAP != nil { - vwap = fmt.Sprint(*s.VWAP) - } - if s.N != nil { - n = fmt.Sprint(*s.N) - } - return fmt.Sprintf("Time: %v, Open: %v, High: %v, Low: %v, Close: %v, Volume: %v, VWAP: %v, N: %v", + return fmt.Sprintf("StockCandlesResponse{Time: %v, Open: %v, High: %v, Low: %v, Close: %v, Volume: %v, VWAP: %v, N: %v}", s.Time, s.Open, s.High, s.Low, s.Close, s.Volume, vwap, n) } } diff --git a/models/stocks_earnings.go b/models/stocks_earnings.go index 6489255..880ac1b 100644 --- a/models/stocks_earnings.go +++ b/models/stocks_earnings.go @@ -12,8 +12,8 @@ import ( // estimated EPS, surprise EPS, surprise EPS percentage, and last updated time (UNIX timestamp). type StockEarningsResponse struct { Symbol []string `json:"symbol"` // Symbol represents the stock symbols. - FiscalYear []int64 `json:"fiscalYear"` // FiscalYear represents the fiscal years of the earnings report. - FiscalQuarter []int64 `json:"fiscalQuarter"` // FiscalQuarter represents the fiscal quarters of the earnings report. + FiscalYear []*int64 `json:"fiscalYear"` // FiscalYear represents the fiscal years of the earnings report. + FiscalQuarter []*int64 `json:"fiscalQuarter"` // FiscalQuarter represents the fiscal quarters of the earnings report. Date []int64 `json:"date"` // Date represents the earnings announcement dates in UNIX timestamp. ReportDate []int64 `json:"reportDate"` // ReportDate represents the report release dates in UNIX timestamp. ReportTime []string `json:"reportTime"` // ReportTime represents the time of day the earnings were reported. @@ -31,8 +31,8 @@ type StockEarningsResponse struct { // surprise EPS, surprise EPS percentage, and the last updated time. type StockEarningsReport struct { Symbol string // Symbol represents the stock symbol. - FiscalYear int64 // FiscalYear represents the fiscal year of the earnings report. - FiscalQuarter int64 // FiscalQuarter represents the fiscal quarter of the earnings report. + FiscalYear *int64 // FiscalYear represents the fiscal year of the earnings report. + FiscalQuarter *int64 // FiscalQuarter represents the fiscal quarter of the earnings report. Date time.Time // Date represents the earnings announcement date. ReportDate time.Time // ReportDate represents the report release date. ReportTime string // ReportTime represents the time of day the earnings were reported. @@ -44,11 +44,6 @@ type StockEarningsReport struct { Updated time.Time // Updated represents the last update time. } -// Unpack converts the StockEarningsResponse struct into a slice of StockEarningsReport structs. -// -// Returns: -// - []StockEarningsReport: A slice of StockEarningsReport structs constructed from the StockEarningsResponse. -// - error: An error if the unpacking process fails, nil otherwise. func (ser StockEarningsReport) String() string { loc, _ := time.LoadLocation("America/New_York") @@ -72,7 +67,7 @@ func (ser StockEarningsReport) String() string { surpriseEPSpct = fmt.Sprintf("%f", *ser.SurpriseEPSpct) } - return fmt.Sprintf("Symbol: %s, Fiscal Year: %v, Fiscal Quarter: %v, Date: %v, Report Date: %v, Report Time: %v, Currency: %v, Reported EPS: %v, Estimated EPS: %v, Surprise EPS: %v, Surprise EPS Pct: %v, Updated: %s", + return fmt.Sprintf("StockEarningsReport{Symbol: %q, FiscalYear: %d, FiscalQuarter: %d, Date: %q, ReportDate: %q, ReportTime: %q, Currency: %q, ReportedEPS: %s, EstimatedEPS: %s, SurpriseEPS: %s, SurpriseEPSPct: %s, Updated: %q}", ser.Symbol, ser.FiscalYear, ser.FiscalQuarter, ser.Date.Format("2006-01-02"), ser.ReportDate.Format("2006-01-02"), ser.ReportTime, ser.Currency, reportedEPS, estimatedEPS, surpriseEPS, surpriseEPSpct, ser.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00")) } @@ -107,17 +102,43 @@ func (ser *StockEarningsResponse) Unpack() ([]StockEarningsReport, error) { // String generates a string representation of the StockEarningsResponse. // -// This method formats the StockEarningsResponse fields into a readable string, including handling nil values for EPS fields gracefully by displaying them as "nil". +// This method formats the StockEarningsResponse fields into a readable string, including handling nil values for EPS fields and empty values for fiscalYear and fiscalQuarter gracefully by displaying them as "nil" or "empty" respectively. // // Returns: // - A string representation of the StockEarningsResponse. func (ser *StockEarningsResponse) String() string { var result strings.Builder - fmt.Fprintf(&result, "Symbol: %v, Fiscal Year: %v, Fiscal Quarter: %v, Date: %v, Report Date: %v, Report Time: %v, Currency: %v, ", - ser.Symbol, ser.FiscalYear, ser.FiscalQuarter, ser.Date, ser.ReportDate, ser.ReportTime, ser.Currency) + fiscalYear := "nil" + if ser.FiscalYear != nil && len(ser.FiscalYear) > 0 { + fiscalYearValues := make([]string, len(ser.FiscalYear)) + for i, v := range ser.FiscalYear { + if v != nil { + fiscalYearValues[i] = fmt.Sprintf("%d", *v) + } else { + fiscalYearValues[i] = "nil" + } + } + fiscalYear = strings.Join(fiscalYearValues, ", ") + } + + fiscalQuarter := "nil" + if ser.FiscalQuarter != nil && len(ser.FiscalQuarter) > 0 { + fiscalQuarterValues := make([]string, len(ser.FiscalQuarter)) + for i, v := range ser.FiscalQuarter { + if v != nil { + fiscalQuarterValues[i] = fmt.Sprintf("%d", *v) + } else { + fiscalQuarterValues[i] = "nil" + } + } + fiscalQuarter = strings.Join(fiscalQuarterValues, ", ") + } + + fmt.Fprintf(&result, "StockEarningsResponse{Symbol: %v, FiscalYear: %v, FiscalQuarter: %v, Date: %v, ReportDate: %v, ReportTime: %v, Currency: %v, ", + ser.Symbol, fiscalYear, fiscalQuarter, ser.Date, ser.ReportDate, ser.ReportTime, ser.Currency) - fmt.Fprintf(&result, "Reported EPS: [") + fmt.Fprintf(&result, "ReportedEPS: [") for _, v := range ser.ReportedEPS { if v != nil { fmt.Fprintf(&result, "%f ", *v) @@ -127,7 +148,7 @@ func (ser *StockEarningsResponse) String() string { } fmt.Fprintf(&result, "], ") - fmt.Fprintf(&result, "Estimated EPS: [") + fmt.Fprintf(&result, "EstimatedEPS: [") for _, v := range ser.EstimatedEPS { if v != nil { fmt.Fprintf(&result, "%f ", *v) @@ -137,7 +158,7 @@ func (ser *StockEarningsResponse) String() string { } fmt.Fprintf(&result, "], ") - fmt.Fprintf(&result, "Surprise EPS: [") + fmt.Fprintf(&result, "SurpriseEPS: [") for _, v := range ser.SurpriseEPS { if v != nil { fmt.Fprintf(&result, "%f ", *v) @@ -147,7 +168,7 @@ func (ser *StockEarningsResponse) String() string { } fmt.Fprintf(&result, "], ") - fmt.Fprintf(&result, "Surprise EPS Pct: [") + fmt.Fprintf(&result, "SurpriseEPSpct: [") for _, v := range ser.SurpriseEPSpct { if v != nil { fmt.Fprintf(&result, "%f ", *v) @@ -157,7 +178,7 @@ func (ser *StockEarningsResponse) String() string { } fmt.Fprintf(&result, "], ") - fmt.Fprintf(&result, "Updated: %v", ser.Updated) + fmt.Fprintf(&result, "Updated: %v}", ser.Updated) return result.String() } diff --git a/models/stocks_news.go b/models/stocks_news.go index 130d409..e963ee0 100644 --- a/models/stocks_news.go +++ b/models/stocks_news.go @@ -36,10 +36,9 @@ type StockNews struct { // - A string representation of the StockNews struct. func (sn StockNews) String() string { loc, _ := time.LoadLocation("America/New_York") - return fmt.Sprintf("Symbol: %s, Headline: %s, Content: %s, Source: %s, PublicationDate: %s", + return fmt.Sprintf("StockNews{Symbol: %q, Headline: %q, Content: %q, Source: %q, PublicationDate: %q}", sn.Symbol, sn.Headline, sn.Content, sn.Source, sn.PublicationDate.In(loc).Format("2006-01-02 15:04:05 Z07:00")) } - // Unpack transforms the StockNewsResponse struct into a slice of StockNews structs. // // Returns: @@ -71,8 +70,9 @@ func (snr *StockNewsResponse) Unpack() ([]StockNews, error) { func (snr *StockNewsResponse) String() string { var result strings.Builder + result.WriteString("StockNewsResponse{") for i := range snr.Symbol { - fmt.Fprintf(&result, "Symbol: %s, Headline: %s, Content: %s, Source: %s, Publication Date: %v", + fmt.Fprintf(&result, "Symbol: %q, Headline: %q, Content: %q, Source: %q, PublicationDate: %v", snr.Symbol[i], snr.Headline[i], snr.Content[i], snr.Source[i], snr.PublicationDate[i]) if i < len(snr.Symbol)-1 { @@ -84,5 +84,6 @@ func (snr *StockNewsResponse) String() string { fmt.Fprintf(&result, ", Updated: %v", snr.Updated) } + result.WriteString("}") return result.String() } diff --git a/models/stocks_quotes.go b/models/stocks_quotes.go index 1e77176..3c28bd8 100644 --- a/models/stocks_quotes.go +++ b/models/stocks_quotes.go @@ -54,19 +54,25 @@ type StockQuote struct { // - A string representation of the StockQuote struct. func (sq StockQuote) String() string { loc, _ := time.LoadLocation("America/New_York") - if sq.High52 != nil && sq.Low52 != nil && sq.Change != nil && sq.ChangePct != nil { - return fmt.Sprintf("Symbol: %s, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, Updated: %s, High52: %v, Low52: %v, Change: %v, ChangePct: %v", - sq.Symbol, sq.Ask, sq.AskSize, sq.Bid, sq.BidSize, sq.Mid, sq.Last, sq.Volume, sq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *sq.High52, *sq.Low52, *sq.Change, *sq.ChangePct) - } else if sq.High52 != nil && sq.Low52 != nil { - return fmt.Sprintf("Symbol: %s, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, Updated: %s, High52: %v, Low52: %v", - sq.Symbol, sq.Ask, sq.AskSize, sq.Bid, sq.BidSize, sq.Mid, sq.Last, sq.Volume, sq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *sq.High52, *sq.Low52) - } else if sq.Change != nil && sq.ChangePct != nil { - return fmt.Sprintf("Symbol: %s, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, Updated: %s, Change: %v, ChangePct: %v", - sq.Symbol, sq.Ask, sq.AskSize, sq.Bid, sq.BidSize, sq.Mid, sq.Last, sq.Volume, sq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00"), *sq.Change, *sq.ChangePct) - } else { - return fmt.Sprintf("Symbol: %s, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, Updated: %s", - sq.Symbol, sq.Ask, sq.AskSize, sq.Bid, sq.BidSize, sq.Mid, sq.Last, sq.Volume, sq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00")) + updatedFormat := sq.Updated.In(loc).Format("2006-01-02 15:04:05 Z07:00") + high52 := "nil" + if sq.High52 != nil { + high52 = fmt.Sprintf("%v", *sq.High52) } + low52 := "nil" + if sq.Low52 != nil { + low52 = fmt.Sprintf("%v", *sq.Low52) + } + change := "nil" + if sq.Change != nil { + change = fmt.Sprintf("%v", *sq.Change) + } + changePct := "nil" + if sq.ChangePct != nil { + changePct = fmt.Sprintf("%v", *sq.ChangePct) + } + return fmt.Sprintf("StockQuote{Symbol: %q, Ask: %v, AskSize: %v, Bid: %v, BidSize: %v, Mid: %v, Last: %v, Volume: %v, Updated: %q, High52: %s, Low52: %s, Change: %s, ChangePct: %s}", + sq.Symbol, sq.Ask, sq.AskSize, sq.Bid, sq.BidSize, sq.Mid, sq.Last, sq.Volume, updatedFormat, high52, low52, change, changePct) } // Unpack transforms the StockQuotesResponse into a slice of StockQuote structs. @@ -121,23 +127,23 @@ func (sqr *StockQuotesResponse) Unpack() ([]StockQuote, error) { func (sqr *StockQuotesResponse) String() string { var result strings.Builder - fmt.Fprintf(&result, "Symbol: %v, Ask: %v, Ask Size: %v, Bid: %v, Bid Size: %v, Mid: %v, Last: %v", - sqr.Symbol, sqr.Ask, sqr.AskSize, sqr.Bid, sqr.BidSize, sqr.Mid, sqr.Last) + fmt.Fprintf(&result, "StockQuotesResponse{Symbol: [%v], Ask: [%v], AskSize: [%v], Bid: [%v], BidSize: [%v], Mid: [%v], Last: [%v]", + strings.Join(sqr.Symbol, ", "), joinFloat64Slice(sqr.Ask), joinInt64Slice(sqr.AskSize), joinFloat64Slice(sqr.Bid), joinInt64Slice(sqr.BidSize), joinFloat64Slice(sqr.Mid), joinFloat64Slice(sqr.Last)) if sqr.Change != nil && len(sqr.Change) > 0 { - fmt.Fprintf(&result, ", Change: %v", sqr.Change[0]) + fmt.Fprintf(&result, ", Change: [%v]", joinFloat64PointerSlice(sqr.Change)) } if sqr.ChangePct != nil && len(sqr.ChangePct) > 0 { - fmt.Fprintf(&result, ", ChangePct: %v", sqr.ChangePct[0]) + fmt.Fprintf(&result, ", ChangePct: [%v]", joinFloat64PointerSlice(sqr.ChangePct)) } if sqr.High52 != nil && len(*sqr.High52) > 0 { - fmt.Fprintf(&result, ", High52: %v", *sqr.High52) + fmt.Fprintf(&result, ", High52: [%v]", joinFloat64Slice(*sqr.High52)) } if sqr.Low52 != nil && len(*sqr.Low52) > 0 { - fmt.Fprintf(&result, ", Low52: %v", *sqr.Low52) + fmt.Fprintf(&result, ", Low52: [%v]", joinFloat64Slice(*sqr.Low52)) } - fmt.Fprintf(&result, ", Volume: %v, Updated: %v", sqr.Volume, sqr.Updated) + fmt.Fprintf(&result, ", Volume: [%v], Updated: [%v]}", joinInt64Slice(sqr.Volume), joinInt64Slice(sqr.Updated)) return result.String() } diff --git a/models/stocks_tickers.go b/models/stocks_tickers.go index f2adbd3..d1d2f98 100644 --- a/models/stocks_tickers.go +++ b/models/stocks_tickers.go @@ -46,12 +46,11 @@ func (tr *TickersResponse) String() string { var str strings.Builder str.WriteString("TickersResponse{\n") for i := range tr.Symbol { - str.WriteString(fmt.Sprintf("Symbol: %s, Name: %s, Type: %s, Currency: %s, Exchange: %s, FigiShares: %s, FigiComposite: %s, Cik: %s\n", tr.Symbol[i], tr.Name[i], tr.Type[i], tr.Currency[i], tr.Exchange[i], tr.FigiShares[i], tr.FigiComposite[i], tr.Cik[i])) + updateTime := "" if tr.Updated != nil && i < len(*tr.Updated) && (*tr.Updated)[i] != 0 { - str.WriteString(fmt.Sprintf("Updated: %v\n", time.Unix((*tr.Updated)[i], 0))) - } else { - str.WriteString("Updated: \n") + updateTime = time.Unix((*tr.Updated)[i], 0).String() } + str.WriteString(fmt.Sprintf("Ticker{Symbol: %q, Name: %q, Type: %q, Currency: %q, Exchange: %q, FigiShares: %q, FigiComposite: %q, Cik: %q, Updated: %q}\n", tr.Symbol[i], tr.Name[i], tr.Type[i], tr.Currency[i], tr.Exchange[i], tr.FigiShares[i], tr.FigiComposite[i], tr.Cik[i], updateTime)) } str.WriteString("}") return str.String() diff --git a/option_chain_test.go b/option_chain_test.go new file mode 100644 index 0000000..571c7e6 --- /dev/null +++ b/option_chain_test.go @@ -0,0 +1,51 @@ +package client + +import ( + "fmt" + "testing" +) + +func TestOptionChainRequest(t *testing.T) { + resp, err := OptionChain().UnderlyingSymbol("AAPL").Side("call").DTE(60).StrikeLimit(2).Range("itm").Get() + if err != nil { + t.Fatalf("Failed to get option chain: %v", err) + } + + if len(resp) < 2 { + t.Fatalf("Expected at least 2 slices, got %d", len(resp)) + } + + for _, contract := range resp { + if contract.Underlying != "AAPL" { + t.Errorf("Expected underlying symbol to be AAPL, got %s", contract.Underlying) + } + if !contract.InTheMoney { + t.Errorf("Expected contract to be in the money, but it was not. Contract: %+v", contract) + } + } +} + +func ExampleOptionChainRequest_packed() { + resp, err := OptionChain().UnderlyingSymbol("AAPL").Side("call").Date("2022-01-03"). + Month(2).Year(2022).Range("itm").Strike(150).Weekly(false).Monthly(true).Quarterly(false).Nonstandard(false).Packed() + if err != nil { + fmt.Println("Error fetching packed option chain:", err) + return + } + fmt.Println(resp) + // Output: OptionQuotesResponse{OptionSymbol: ["AAPL220121C00150000"], Underlying: ["AAPL"], Expiration: [1642798800], Side: ["call"], Strike: [150], FirstTraded: [1568640600], DTE: [18], Ask: [32.15], AskSize: [2], Bid: [31.8], BidSize: [359], Mid: [31.98], Last: [32], Volume: [3763], OpenInterest: [98804], UnderlyingPrice: [182.01], InTheMoney: [true], Updated: [1641243600], IV: [nil], Delta: [nil], Gamma: [], Theta: [nil], Vega: [nil], Rho: [nil], IntrinsicValue: [32.01], ExtrinsicValue: [0.03]} +} + + +func ExampleOptionChainRequest_get() { + resp, err := OptionChain().UnderlyingSymbol("AAPL").Side("call").Date("2022-01-03").DTE(60).StrikeLimit(2).Range("itm").Get() + if err != nil { + fmt.Println("Error fetching option chain:", err) + return + } + for _, contract := range resp { + fmt.Println(contract) + } + // Output: OptionQuote{OptionSymbol: "AAPL220318C00175000", Underlying: "AAPL", Expiration: 2022-03-18 16:00:00 -04:00, Side: "call", Strike: 175, FirstTraded: 2021-07-13 09:30:00 -04:00, DTE: 73, Ask: 13.1, AskSize: 2, Bid: 12.95, BidSize: 3, Mid: 13.02, Last: 12.9, Volume: 1295, OpenInterest: 15232, UnderlyingPrice: 182.01, InTheMoney: true, Updated: "2022-01-03 16:00:00 -05:00", IV: nil, Delta: nil, Gamma: nil, Theta: nil, Vega: nil, Rho: nil, IntrinsicValue: 7.01, ExtrinsicValue: 6.02} + // OptionQuote{OptionSymbol: "AAPL220318C00180000", Underlying: "AAPL", Expiration: 2022-03-18 16:00:00 -04:00, Side: "call", Strike: 180, FirstTraded: 2021-07-13 09:30:00 -04:00, DTE: 73, Ask: 10.2, AskSize: 12, Bid: 10, BidSize: 38, Mid: 10.1, Last: 10.1, Volume: 4609, OpenInterest: 18299, UnderlyingPrice: 182.01, InTheMoney: true, Updated: "2022-01-03 16:00:00 -05:00", IV: nil, Delta: nil, Gamma: nil, Theta: nil, Vega: nil, Rho: nil, IntrinsicValue: 2.01, ExtrinsicValue: 8.09} +} diff --git a/options_expirations.go b/options_expirations.go index 9cb8de8..dd2e77e 100644 --- a/options_expirations.go +++ b/options_expirations.go @@ -19,6 +19,27 @@ type OptionsExpirationsRequest struct { *baseRequest underlyingSymbol *parameters.SymbolParams strike *parameters.OptionParams + dateParams *parameters.DateParams +} + +// Date sets the date parameter for the OptionsExpirationsRequest. +// This method is used to specify the date for which the options expirations are requested. +// It modifies the dateParams field of the OptionsExpirationsRequest instance to store the date value. +// +// Parameters: +// - q: An interface{} representing the date to be set. +// +// Returns: +// - *OptionsExpirationsRequest: This method returns a pointer to the OptionsExpirationsRequest instance it was called on. This allows for method chaining. If the receiver (*OptionsExpirationsRequest) is nil, it returns nil to prevent a panic. +func (oer *OptionsExpirationsRequest) Date(q interface{}) *OptionsExpirationsRequest { + if oer.dateParams == nil { + oer.dateParams = ¶meters.DateParams{} + } + err := oer.dateParams.SetDate(q) + if err != nil { + oer.Error = err + } + return oer } // Strike sets the strike price parameter for the OptionsExpirationsRequest. @@ -61,7 +82,7 @@ func (o *OptionsExpirationsRequest) getParams() ([]parameters.MarketDataParam, e if o == nil { return nil, fmt.Errorf("OptionsExpirationsRequest is nil") } - params := []parameters.MarketDataParam{o.underlyingSymbol, o.strike} + params := []parameters.MarketDataParam{o.underlyingSymbol, o.strike, o.dateParams} return params, nil } @@ -138,6 +159,7 @@ func OptionsExpirations(client ...*MarketDataClient) *OptionsExpirationsRequest baseRequest: baseReq, underlyingSymbol: ¶meters.SymbolParams{}, strike: ¶meters.OptionParams{}, + dateParams: ¶meters.DateParams{}, } baseReq.child = oer diff --git a/options_expirations_test.go b/options_expirations_test.go new file mode 100644 index 0000000..2891113 --- /dev/null +++ b/options_expirations_test.go @@ -0,0 +1,32 @@ +package client + +import "fmt" + +func ExampleOptionsExpirationsResponse_get() { + resp, err := OptionsExpirations().UnderlyingSymbol("AAPL").Date("2009-02-04").Get() + if err != nil { + fmt.Print(err) + return + } + + for _, expirations := range resp { + fmt.Println(expirations) + } + // Output: 2009-02-21 16:00:00 -0500 EST + // 2009-03-21 16:00:00 -0400 EDT + // 2009-04-18 16:00:00 -0400 EDT + // 2009-07-18 16:00:00 -0400 EDT + // 2010-01-16 16:00:00 -0500 EST + // 2011-01-22 16:00:00 -0500 EST +} + +func ExampleOptionsExpirationsResponse_packed() { + resp, err := OptionsExpirations().UnderlyingSymbol("AAPL").Date("2009-02-04").Packed() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: OptionsExpirationsResponse{Expirations: [2009-02-21 2009-03-21 2009-04-18 2009-07-18 2010-01-16 2011-01-22], Updated: 1233723600} +} diff --git a/options_lookup_test.go b/options_lookup_test.go new file mode 100644 index 0000000..9925983 --- /dev/null +++ b/options_lookup_test.go @@ -0,0 +1,25 @@ +package client + +import "fmt" + +func ExampleOptionLookupRequest_get() { + resp, err := OptionLookup().UserInput("AAPL 7/28/2023 200 Call").Get() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: AAPL230728C00200000 +} + +func ExampleOptionLookupRequest_packed() { + resp, err := OptionLookup().UserInput("AAPL 7/28/2023 200 Call").Packed() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: OptionLookupResponse{OptionSymbol: "AAPL230728C00200000"} +} diff --git a/options_quotes.go b/options_quotes.go index e8285cb..96573b3 100644 --- a/options_quotes.go +++ b/options_quotes.go @@ -105,7 +105,7 @@ func (oqr *OptionQuoteRequest) getParams() ([]parameters.MarketDataParam, error) if oqr == nil { return nil, fmt.Errorf("OptionQuoteRequest is nil") } - params := []parameters.MarketDataParam{oqr.symbolParams} + params := []parameters.MarketDataParam{oqr.symbolParams, oqr.dateParams} return params, nil } diff --git a/options_quotes_test.go b/options_quotes_test.go new file mode 100644 index 0000000..dfcaa9b --- /dev/null +++ b/options_quotes_test.go @@ -0,0 +1,28 @@ +package client + +import "fmt" + +func ExampleOptionQuoteRequest_get() { + resp, err := OptionQuote().OptionSymbol("AAPL250117C00150000").Date("2024-02-05").Get() + if err != nil { + fmt.Print(err) + return + } + + for _, quote := range resp { + fmt.Println(quote) + } + // Output: OptionQuote{OptionSymbol: "AAPL250117C00150000", Underlying: "AAPL", Expiration: 2025-01-17 16:00:00 -05:00, Side: "call", Strike: 150, FirstTraded: 2022-09-10 09:30:00 -04:00, DTE: 347, Ask: 47.7, AskSize: 17, Bid: 47.2, BidSize: 36, Mid: 47.45, Last: 48.65, Volume: 202, OpenInterest: 10768, UnderlyingPrice: 187.68, InTheMoney: true, Updated: "2024-02-05 16:00:00 -05:00", IV: nil, Delta: nil, Gamma: nil, Theta: nil, Vega: nil, Rho: nil, IntrinsicValue: 37.68, ExtrinsicValue: 9.77} +} + +func ExampleOptionQuoteRequest_packed() { + resp, err := OptionQuote().OptionSymbol("AAPL250117P00150000").Date("2024-02-05").Packed() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: OptionQuotesResponse{OptionSymbol: ["AAPL250117P00150000"], Underlying: ["AAPL"], Expiration: [1737147600], Side: ["put"], Strike: [150], FirstTraded: [1662816600], DTE: [347], Ask: [3.65], AskSize: [292], Bid: [3.5], BidSize: [634], Mid: [3.58], Last: [3.55], Volume: [44], OpenInterest: [18027], UnderlyingPrice: [187.68], InTheMoney: [false], Updated: [1707166800], IV: [], Delta: [], Gamma: [], Theta: [], Vega: [], Rho: [], IntrinsicValue: [0], ExtrinsicValue: [3.58]} +} + diff --git a/options_strikes_test.go b/options_strikes_test.go new file mode 100644 index 0000000..c6176a5 --- /dev/null +++ b/options_strikes_test.go @@ -0,0 +1,32 @@ +package client + +import "fmt" + +func ExampleOptionsStrikesRequest_get() { + resp, err := OptionsStrikes().UnderlyingSymbol("AAPL").Date("2009-02-09").Get() + if err != nil { + fmt.Print(err) + return + } + + for _, expiration := range resp { + fmt.Println(expiration) + } + // Output: OptionsStrikes{Expiration: 2009-02-21, Strikes: [40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00]} + // OptionsStrikes{Expiration: 2009-03-21, Strikes: [35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00]} + // OptionsStrikes{Expiration: 2009-04-18, Strikes: [15.00 17.50 20.00 22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00 195.00 200.00 210.00 220.00 230.00 240.00 250.00 260.00 270.00 280.00 290.00 300.00]} + // OptionsStrikes{Expiration: 2009-07-18, Strikes: [12.50 15.00 17.50 20.00 22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00]} + // OptionsStrikes{Expiration: 2010-01-16, Strikes: [22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 170.00 180.00 190.00 200.00 210.00 220.00 230.00 240.00 250.00 260.00 270.00 280.00 290.00 300.00 310.00 320.00 330.00 340.00 350.00 360.00 370.00 380.00 390.00 400.00]} + // OptionsStrikes{Expiration: 2011-01-22, Strikes: [20.00 30.00 40.00 50.00 60.00 70.00 80.00 85.00 90.00 95.00 100.00 110.00 120.00 125.00 130.00 140.00 150.00 160.00 170.00 180.00 190.00 200.00 210.00 220.00 230.00 240.00]} +} + +func ExampleOptionsStrikesRequest_packed() { + resp, err := OptionsStrikes().UnderlyingSymbol("AAPL").Date("2009-02-09").Packed() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: OptionsStrikesResponse{Strikes: [2009-02-21:[40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00], 2009-03-21:[35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00], 2009-04-18:[15.00 17.50 20.00 22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00 195.00 200.00 210.00 220.00 230.00 240.00 250.00 260.00 270.00 280.00 290.00 300.00], 2009-07-18:[12.50 15.00 17.50 20.00 22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 115.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00], 2010-01-16:[22.50 25.00 30.00 35.00 40.00 45.00 50.00 55.00 60.00 65.00 70.00 75.00 80.00 85.00 90.00 95.00 100.00 105.00 110.00 120.00 125.00 130.00 135.00 140.00 145.00 150.00 155.00 160.00 170.00 180.00 190.00 200.00 210.00 220.00 230.00 240.00 250.00 260.00 270.00 280.00 290.00 300.00 310.00 320.00 330.00 340.00 350.00 360.00 370.00 380.00 390.00 400.00], 2011-01-22:[20.00 30.00 40.00 50.00 60.00 70.00 80.00 85.00 90.00 95.00 100.00 110.00 120.00 125.00 130.00 140.00 150.00 160.00 170.00 180.00 190.00 200.00 210.00 220.00 230.00 240.00]], Updated: 1234155600} +} \ No newline at end of file