From fd01fd301b4695978c57231983b3fb77577e45c1 Mon Sep 17 00:00:00 2001 From: MarketDataApp Date: Thu, 2 May 2024 19:17:50 -0300 Subject: [PATCH] added mutual fund candles --- .scripts/gomarkdoc.sh | 1 + .scripts/process_markdown.py | 1 + README.md | 10 +- baseRequest.go | 8 + endpoints.go | 3 + funds_candles.go | 282 +++++++++++++++++++++ funds_candles_test.go | 41 +++ go.mod | 10 +- go.sum | 42 ++-- indices_candles_test.go | 19 +- models/funds_candles.go | 465 +++++++++++++++++++++++++++++++++++ models/funds_candles_test.go | 356 +++++++++++++++++++++++++++ models/options_quotes.go | 7 +- options_chain_test.go | 6 +- options_quotes_test.go | 12 +- stocks_candles_test.go | 13 +- stocks_candles_v2_test.go | 18 +- 17 files changed, 1237 insertions(+), 57 deletions(-) create mode 100644 funds_candles.go create mode 100644 funds_candles_test.go create mode 100644 models/funds_candles.go create mode 100644 models/funds_candles_test.go diff --git a/.scripts/gomarkdoc.sh b/.scripts/gomarkdoc.sh index 10e9510..46433f0 100755 --- a/.scripts/gomarkdoc.sh +++ b/.scripts/gomarkdoc.sh @@ -159,6 +159,7 @@ process_group "options_lookup" process_group "options_quotes" process_group "options_strikes" process_group "options_chain" +process_group "funds_candles" process_group "client" process_group "logging" diff --git a/.scripts/process_markdown.py b/.scripts/process_markdown.py index 2081dff..e881aac 100755 --- a/.scripts/process_markdown.py +++ b/.scripts/process_markdown.py @@ -19,6 +19,7 @@ "https://www.marketdata.app/docs/api/options/strikes": {"title": "Strikes", "sidebar_position": 3}, "https://www.marketdata.app/docs/api/options/chain": {"title": "Option Chain", "sidebar_position": 4}, "https://www.marketdata.app/docs/api/options/quotes": {"title": "Quotes", "sidebar_position": 5}, + "https://www.marketdata.app/docs/api/funds/candles": {"title": "Candles", "sidebar_position": 1}, "https://www.marketdata.app/docs/sdk/go/client": {"title": "Client", "sidebar_position": 2}, "https://www.marketdata.app/docs/sdk/go/logging": {"title": "Logging", "sidebar_position": 3}, diff --git a/README.md b/README.md index 7bd81c9..b627835 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-# Go SDK for Market Data v1.1 +# Go SDK for Market Data v1.2 ### Access Financial Data with Ease > This is the official Go SDK for [Market Data](https://www.marketdata.app/). It provides developers with a powerful, easy-to-use interface to obtain real-time and historical financial data. Ideal for building financial applications, trading bots, and investment strategies. @@ -12,7 +12,7 @@ [![License](https://img.shields.io/github/license/MarketDataApp/sdk-go.svg)](https://github.com/MarketDataApp/sdk-go/blob/master/LICENSE) ![SDK Version](https://img.shields.io/badge/version-1.0.0-blue.svg) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/MarketDataApp/sdk-go) -![Lines of Code](https://img.shields.io/badge/lines_of_code-8688-blue) +![Lines of Code](https://img.shields.io/badge/lines_of_code-8710-blue) #### Connect With The Market Data Community @@ -235,10 +235,14 @@ Market Data's Go SDK covers the vast majority of v1 endpoints. See our complete | | [Candles](https://www.marketdata.app/docs/api/indices/candles) | ✅ | ❌ | | | [Quotes](https://www.marketdata.app/docs/api/indices/quotes) | ✅ | ❌ | | | | | | + | **[FUNDS](https://www.marketdata.app/docs/api/funds)** | | | | + | | [Candles](https://www.marketdata.app/docs/api/indices/candles) | ✅ | ❌ | + | | | | | | **[UTILITIES](https://www.marketdata.app/docs/api/utilities)** | | | | | | [Status](https://www.marketdata.app/docs/api/utilities/status) | ❌ | ❌ | + | | [Headers](https://www.marketdata.app/docs/api/utilities/headers) | ❌ | ❌ | -> Note on v2: Even though some v2 endpoints are available for use in this SDK, Market Data has not yet released v2 of its API for clients and v2 usage is restricted to admins only. Clients should only use v1 endpoints at this time. Even after v2 is released, we do not plan on deprecating v1 endpoints, so please build your applications with confidence using v1 endpoints. +> Note on v2: Even though some v2 endpoints are available for use in this SDK, Market Data has not yet released v2 of its API for clients and v2 usage is restricted to admins only. Clients should only use v1 endpoints at this time. Even after v2 is released, we do not plan on deprecating v1 endpoints, so please build your applications with confidence using our v1 endpoints. ### SDK License & Data Usage Terms diff --git a/baseRequest.go b/baseRequest.go index 4a141e9..8579fa2 100644 --- a/baseRequest.go +++ b/baseRequest.go @@ -202,6 +202,14 @@ func (br *baseRequest) getParams() ([]parameters.MarketDataParam, error) { return params, nil } + if fcr, ok := br.child.(*FundCandlesRequest); ok { + params, err := fcr.getParams() + if err != nil { + return nil, err + } + return params, nil + } + return []parameters.MarketDataParam{}, nil } diff --git a/endpoints.go b/endpoints.go index 234dee1..e9ab3cb 100644 --- a/endpoints.go +++ b/endpoints.go @@ -28,6 +28,9 @@ var endpoints = map[int]map[string]map[string]string{ "quotes": "/v1/indices/quotes/{symbol}/", "candles": "/v1/indices/candles/{resolution}/{symbol}/", }, + "funds": { + "candles": "/v1/funds/candles/{resolution}/{symbol}/", + }, }, 2: { "stocks": { diff --git a/funds_candles.go b/funds_candles.go new file mode 100644 index 0000000..172bb00 --- /dev/null +++ b/funds_candles.go @@ -0,0 +1,282 @@ +// Package client includes types and methods to access the Funds / Candles endpoint. Retrieve historical price candles for any supported stock symbol. +// +// # Making Requests +// +// Use [FundCandlesRequest] to make requests to the endpoint using any of the three supported execution methods: +// +// | Method | Execution | Return Type | Description | +// |------------|---------------|-----------------------------|------------------------------------------------------------------------------------------------------------| +// | **Get** | Direct | `[]Candle` | Directly returns a slice of `[]Candle`, facilitating individual access to each candle. | +// | **Packed** | Intermediate | `*FundCandlesResponse` | Returns a packed `*FundCandlesResponse` object. Must be unpacked to access the `[]Candle` slice. | +// | **Raw** | Low-level | `*resty.Response` | Provides the raw `*resty.Response` for maximum flexibility. Direct access to raw JSON or `*http.Response`. | +package client + +import ( + "fmt" + + "github.com/MarketDataApp/sdk-go/helpers/parameters" + "github.com/MarketDataApp/sdk-go/models" + "github.com/go-resty/resty/v2" +) + +// FundCandlesRequest represents a request to the [/v1/funds/candles/] endpoint. +// It encapsulates parameters for resolution, symbol, date, and additional stock-specific parameters to be used in the request. +// This struct provides methods such as Resolution(), Symbol(), Date(), From(), To(), Countback(), AdjustSplits(), AdjustDividends(), Extended(), and Exchange() to set these parameters respectively. +// +// # Generated By +// +// - FundCandles() *FundCandlesRequest: FundCandles creates a new *FundCandlesRequest and returns a pointer to the request allowing for method chaining. +// +// # Setter Methods +// +// - Resolution(string) *FundCandlesRequest: Sets the resolution parameter for the request. +// - Symbol(string) *FundCandlesRequest: Sets the symbol parameter for the request. +// - Date(interface{}) *FundCandlesRequest: Sets the date parameter for the request. +// - From(interface{}) *FundCandlesRequest: Sets the 'from' date parameter for the request. +// - To(interface{}) *FundCandlesRequest: Sets the 'to' date parameter for the request. +// - Countback(int) *FundCandlesRequest: Sets the countback parameter for the request. +// - AdjustSplits(bool) *FundCandlesRequest: Sets the adjust splits parameter for the request. +// - AdjustDividends(bool) *FundCandlesRequest: Sets the adjust dividends parameter for the request. +// - Extended(bool) *FundCandlesRequest: Sets the extended hours data parameter for the request. +// - Exchange(string) *FundCandlesRequest: Sets the exchange parameter for the request. +// +// # Execution Methods +// +// These methods are used to send the request in different formats or retrieve the data. +// They handle the actual communication with the API endpoint. +// +// - Get() ([]Candle, error): Sends the request, unpacks the response, and returns the data in a user-friendly format. +// - Packed() (*FundCandlesResponse, error): Returns a struct that contains equal-length slices of primitives. This packed response mirrors Market Data's JSON response. +// - Raw() (*resty.Response, error): Sends the request as is and returns the raw HTTP response. +// +// [/v1/funds/candles/]: https://www.marketdata.app/docs/api/funds/candles +type FundCandlesRequest struct { + *baseRequest + stockCandleParams *parameters.StockCandleParams + resolutionParams *parameters.ResolutionParams + symbolParams *parameters.SymbolParams + dateParams *parameters.DateParams +} + +// Resolution sets the resolution parameter for the FundCandlesRequest. +// This method is used to specify the granularity of the candle data to be retrieved. +// +// # Parameters +// +// - string: A string representing the resolution to be set. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (cr *FundCandlesRequest) Resolution(q string) *FundCandlesRequest { + if cr == nil { + return nil + } + err := cr.resolutionParams.SetResolution(q) + if err != nil { + cr.Error = err + } + return cr +} + +// Symbol sets the symbol parameter for the FundCandlesRequest. +// This method is used to specify the stock symbol for which candle data is requested. +// +// # Parameters +// +// - string: A string representing the stock symbol to be set. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (fcr *FundCandlesRequest) Symbol(q string) *FundCandlesRequest { + if fcr == nil { + return nil + } + err := fcr.symbolParams.SetSymbol(q) + if err != nil { + fcr.Error = err + } + return fcr +} + +// Date sets the date parameter for the FundCandlesRequest. +// This method is used to specify the date for which the stock candle data is requested. +// +// # Parameters +// +// - interface{}: An interface{} representing the date to be set. It can be a string, a time.Time object, a Unix timestamp as an int, or any other type that the underlying dates package method can process. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (fcr *FundCandlesRequest) Date(q interface{}) *FundCandlesRequest { + err := fcr.dateParams.SetDate(q) + if err != nil { + fcr.baseRequest.Error = err + } + return fcr +} + +// From sets the 'from' date parameter for the FundCandlesRequest. +// This method is used to specify the starting point of the date range for which the stock candle data is requested. +// +// # Parameters +// +// - interface{}: An interface{} representing the date to be set. It can be a string, a time.Time object, a Unix timestamp as an int, or any other type that the underlying dates package method can process. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (fcr *FundCandlesRequest) From(q interface{}) *FundCandlesRequest { + err := fcr.dateParams.SetFrom(q) + if err != nil { + fcr.baseRequest.Error = err + } + return fcr +} + +// To sets the 'to' date parameter for the FundCandlesRequest. +// This method is used to specify the ending point of the date range for which the stock candle data is requested. +// +// # Parameters +// +// - interface{}: An interface{} representing the date to be set. It can be a string, a time.Time object, a Unix timestamp as an int, or any other type that the underlying dates package method can process. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on +func (fcr *FundCandlesRequest) To(q interface{}) *FundCandlesRequest { + err := fcr.dateParams.SetTo(q) + if err != nil { + fcr.baseRequest.Error = err + } + return fcr +} + +// Countback sets the countback parameter for the FundCandlesRequest. +// This method specifies the number of candles to return, counting backwards from the 'to' date. +// +// # Parameters +// +// - int: The number of candles to return. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (fcr *FundCandlesRequest) Countback(q int) *FundCandlesRequest { + err := fcr.dateParams.SetCountback(q) + if err != nil { + fcr.baseRequest.Error = err + } + return fcr +} + +// AdjustSplits sets the adjust splits parameter for the FundCandlesRequest. +// This method indicates whether the returned data should be adjusted for stock splits. +// +// # Parameters +// +// - bool: Whether to adjust for splits. +// +// # Returns +// +// - *FundCandlesRequest: This method returns a pointer to the *FundCandlesRequest instance it was called on. This allows for method chaining. +func (fcr *FundCandlesRequest) AdjustSplits(q bool) *FundCandlesRequest { + if fcr == nil { + return nil + } + fcr.stockCandleParams.SetAdjustSplits(q) + return fcr +} + +// getParams packs the CandlesRequest struct into a slice of interface{} and returns it. +func (fcr *FundCandlesRequest) getParams() ([]parameters.MarketDataParam, error) { + if fcr == nil { + return nil, fmt.Errorf("FundCandlesRequest is nil") + } + params := []parameters.MarketDataParam{fcr.dateParams, fcr.symbolParams, fcr.resolutionParams} + return params, nil +} + +// Raw executes the FundCandlesRequest and returns the raw *resty.Response. +// This method returns the raw JSON or *http.Response for further processing without accepting an alternative MarketDataClient. +// +// # Returns +// +// - *resty.Response: The raw HTTP response from the executed request. +// - error: An error object if the request fails due to execution errors. +func (fcr *FundCandlesRequest) Raw() (*resty.Response, error) { + return fcr.baseRequest.Raw() +} + +// Packed sends the FundCandlesRequest and returns the FundCandlesResponse. +//// +// # Returns +// +// - *FundCandlesResponse: A pointer to the FundCandlesResponse obtained from the request. +// - error: An error object that indicates a failure in sending the request. +func (fcr *FundCandlesRequest) Packed() (*models.FundCandlesResponse, error) { + if fcr == nil { + return nil, fmt.Errorf("FundCandlesRequest is nil") + } + + var fcrResp models.FundCandlesResponse + _, err := fcr.baseRequest.client.getFromRequest(fcr.baseRequest, &fcrResp) + if err != nil { + return nil, err + } + + return &fcrResp, nil +} + +// Get sends the FundCandlesRequest, unpacks the FundCandlesResponse, and returns a slice of StockCandle. +// It returns an error if the request or unpacking fails. +// +// # Returns +// +// - []Candle: A slice of []Candle containing the unpacked candle data from the response. +// - error: An error object that indicates a failure in sending the request or unpacking the response. +func (fcr *FundCandlesRequest) Get() ([]models.Candle, error) { + if fcr == nil { + return nil, fmt.Errorf("FundCandlesRequest is nil") + } + + // Use the Packed method to make the request + fcrResp, err := fcr.Packed() + if err != nil { + return nil, err + } + + // Unpack the data using the Unpack method in the response + data, err := fcrResp.Unpack() + if err != nil { + return nil, err + } + + return data, nil +} + +// FundCandles initializes a new FundCandlesRequest with default parameters. +// This function prepares a request to fetch stock candle data. It sets up all necessary parameters +// and configurations to make the request ready to be sent. +// +// # Returns +// +// - *FundCandlesRequest: A pointer to the newly created FundCandlesRequest instance. This instance contains all the necessary parameters set to their default values and is ready to have additional parameters set or to be sent. +func FundCandles() *FundCandlesRequest { + baseReq := newBaseRequest() + baseReq.path = endpoints[1]["funds"]["candles"] + + fcr := &FundCandlesRequest{ + baseRequest: baseReq, + dateParams: ¶meters.DateParams{}, + resolutionParams: ¶meters.ResolutionParams{}, + symbolParams: ¶meters.SymbolParams{}, + } + + // Set the date to the current time + baseReq.child = fcr + + return fcr +} diff --git a/funds_candles_test.go b/funds_candles_test.go new file mode 100644 index 0000000..750eb02 --- /dev/null +++ b/funds_candles_test.go @@ -0,0 +1,41 @@ +package client + +import "fmt" + +func ExampleFundCandlesRequest_raw() { + fcr, err := FundCandles().Resolution("D").Symbol("VFINX").From("2023-01-01").To("2023-01-06").Raw() + if err != nil { + fmt.Print(err) + return + } + fmt.Println(fcr) + + // Output: {"s":"ok","t":[1672722000,1672808400,1672894800,1672981200],"o":[352.76,355.43,351.35,359.38],"h":[352.76,355.43,351.35,359.38],"l":[352.76,355.43,351.35,359.38],"c":[352.76,355.43,351.35,359.38]} +} + +func ExampleFundCandlesRequest_packed() { + fcr, err := FundCandles().Resolution("D").Symbol("VFINX").From("2023-01-01").To("2023-01-06").Packed() + if err != nil { + fmt.Print(err) + return + } + fmt.Println(fcr) + + // Output: FundCandlesResponse{Date: [1672722000 1672808400 1672894800 1672981200], Open: [352.76 355.43 351.35 359.38], High: [352.76 355.43 351.35 359.38], Low: [352.76 355.43 351.35 359.38], Close: [352.76 355.43 351.35 359.38]} +} + +func ExampleFundCandlesRequest_get() { + fcr, err := FundCandles().Resolution("D").Symbol("VFINX").From("2023-01-01").To("2023-01-06").Get() + if err != nil { + fmt.Print(err) + return + } + + for _, candle := range fcr { + fmt.Println(candle) + } + // Output: Candle{Date: 2023-01-03, Open: 352.76, High: 352.76, Low: 352.76, Close: 352.76} + // Candle{Date: 2023-01-04, Open: 355.43, High: 355.43, Low: 355.43, Close: 355.43} + // Candle{Date: 2023-01-05, Open: 351.35, High: 351.35, Low: 351.35, Close: 351.35} + // Candle{Date: 2023-01-06, Open: 359.38, High: 359.38, Low: 359.38, Close: 359.38} +} diff --git a/go.mod b/go.mod index 85160fb..0429469 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require github.com/joho/godotenv v1.5.1 require ( github.com/fatih/color v1.16.0 - github.com/go-resty/resty/v2 v2.11.0 + github.com/go-resty/resty/v2 v2.12.0 github.com/iancoleman/orderedmap v0.3.0 github.com/stretchr/testify v1.8.1 - go.uber.org/zap v1.26.0 + go.uber.org/zap v1.27.0 ) require ( @@ -17,8 +17,8 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d9047eb..9821330 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= -github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= +github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -24,15 +24,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -40,9 +41,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -55,22 +57,24 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/indices_candles_test.go b/indices_candles_test.go index 017c09b..ee4e41d 100644 --- a/indices_candles_test.go +++ b/indices_candles_test.go @@ -3,7 +3,7 @@ package client import "fmt" func ExampleIndicesCandlesRequest_get() { - vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2022-01-01").To("2022-01-05").Get() + vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2024-01-01").To("2024-01-05").Get() if err != nil { println("Error retrieving VIX index candles:", err.Error()) return @@ -12,29 +12,30 @@ func ExampleIndicesCandlesRequest_get() { for _, candle := range vix { fmt.Println(candle) } - // Output: Candle{Date: 2022-01-03, Open: 17.6, High: 18.54, Low: 16.56, Close: 16.6} - // Candle{Date: 2022-01-04, Open: 16.57, High: 17.81, Low: 16.34, Close: 16.91} - // Candle{Date: 2022-01-05, Open: 17.07, High: 20.17, Low: 16.58, Close: 19.73} - + // Output: Candle{Date: 2024-01-02, Open: 13.21, High: 14.23, Low: 13.1, Close: 13.2} + // Candle{Date: 2024-01-03, Open: 13.38, High: 14.22, Low: 13.36, Close: 14.04} + // Candle{Date: 2024-01-04, Open: 13.97, High: 14.2, Low: 13.64, Close: 14.13} + // Candle{Date: 2024-01-05, Open: 14.24, High: 14.58, Low: 13.29, Close: 13.35} } func ExampleIndicesCandlesRequest_packed() { - vix, err := IndexCandles().Symbol("VIX").Resolution("D").To("2022-01-05").Countback(3).Packed() + vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2024-01-01").To("2024-01-05").Packed() if err != nil { println("Error retrieving VIX index candles:", err.Error()) return } fmt.Println(vix) - // 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]} + // Output: IndicesCandlesResponse{Time: [1704171600 1704258000 1704344400 1704430800], Open: [13.21 13.38 13.97 14.24], High: [14.23 14.22 14.2 14.58], Low: [13.1 13.36 13.64 13.29], Close: [13.2 14.04 14.13 13.35]} } func ExampleIndicesCandlesRequest_raw() { - vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2022-01-01").To("2022-01-05").Raw() + vix, err := IndexCandles().Symbol("VIX").Resolution("D").From("2024-01-01").To("2024-01-05").Raw() if err != nil { println("Error retrieving VIX index candles:", err.Error()) return } fmt.Println(vix) - // Output: {"s":"ok","t":[1641186000,1641272400,1641358800],"o":[17.6,16.57,17.07],"h":[18.54,17.81,20.17],"l":[16.56,16.34,16.58],"c":[16.6,16.91,19.73]} + // Output: {"s":"ok","t":[1704171600,1704258000,1704344400,1704430800],"o":[13.21,13.38,13.97,14.24],"h":[14.23,14.22,14.2,14.58],"l":[13.1,13.36,13.64,13.29],"c":[13.2,14.04,14.13,13.35]} + } diff --git a/models/funds_candles.go b/models/funds_candles.go new file mode 100644 index 0000000..10183c9 --- /dev/null +++ b/models/funds_candles.go @@ -0,0 +1,465 @@ +package models + +import ( + "encoding/json" + "fmt" + "sort" + "time" + + "github.com/MarketDataApp/sdk-go/helpers/dates" + "github.com/iancoleman/orderedmap" +) + +// FundCandlesResponse encapsulates the data structure for the JSON response of mutual fund candles data. It includes +// detailed information about fund prices at different times, such as opening, closing, highest, and lowest prices, +// along with the trading volume. For version 2 candles, it optionally includes the Volume Weighted Average Price (VWAP) +// and the number of trades. +// +// # Generated By +// +// - FundCandlesRequest.Packed(): Sends a FundCandlesRequest and unmarshals the response into FundCandlesResponse. +// +// # Methods +// +// - Unpack() ([]Candle, error): Converts a FundCandlesResponse into a slice of Candle. +// - String() string: Returns a string representation of the FundCandlesResponse. +// - IsValid() bool: Checks if a FundCandlesResponse is valid. +// - Validate() error: Validates a FundCandlesResponse. +// - MarshalJSON() ([]byte, error): Marshals a FundCandlesResponse into JSON. +// - UnmarshalJSON(data []byte) error: Unmarshals JSON into a FundCandlesResponse. +// - GetDateRange() (dates.DateRange, error): Returns the date range of a FundCandlesResponse. +// - PruneOutsideDateRange(dr dates.DateRange) error: Removes data points outside a specified date range. +// +// # Notes +// +// - The optional fields VWAP and N are only available for version 2 candles. +// - The Date field uses UNIX timestamps to represent the date and time of each candle. +type FundCandlesResponse struct { + Date []int64 `json:"t"` // Date holds UNIX timestamps for each candle. + Open []float64 `json:"o"` // Open holds the opening prices for each candle. + High []float64 `json:"h"` // High holds the highest prices reached in each candle. + Low []float64 `json:"l"` // Low holds the lowest prices reached in each candle. + Close []float64 `json:"c"` // Close holds the closing prices for each candle. +} + +// Unpack converts a FundCandlesResponse into a slice of Candle. This method is primarily used to transform the aggregated +// mutual fund candles data from a structured response format into a more accessible slice of individual candle data. It allows users +// to iterate over or manipulate the mutual fund candle data more conveniently in their applications. +// +// # Returns +// +// - []Candle: A slice of Candle containing the unpacked data from the FundCandlesResponse. +// - error: An error if the slices within FundCandlesResponse are not of equal length, indicating inconsistent data. +// +// # Notes +// +// - This method ensures that all slices within the FundCandlesResponse have the same length before unpacking to prevent data misalignment. +func (fcr *FundCandlesResponse) Unpack() ([]Candle, error) { + if err := fcr.checkForEqualSlices(); err != nil { + return nil, err + } + + var fundCandles []Candle + for i := range fcr.Date { + fundCandle := Candle{ + Date: time.Unix(fcr.Date[i], 0), + Open: fcr.Open[i], + High: fcr.High[i], + Low: fcr.Low[i], + Close: fcr.Close[i], + } + fundCandles = append(fundCandles, fundCandle) + } + return fundCandles, nil +} + +// String generates a string representation of a FundCandlesResponse. This method is primarily used for logging or debugging purposes, allowing developers to easily view the contents of a FundCandlesResponse in a human-readable format. +// +// # Returns +// +// - string: A string representation of the FundCandlesResponse. +// +// # Notes +// +// - Mutual fund candles do not include Volume, VWAP, or transaction quantities (n). +func (f *FundCandlesResponse) String() string { + return fmt.Sprintf("FundCandlesResponse{Date: %v, Open: %v, High: %v, Low: %v, Close: %v}", + f.Date, f.Open, f.High, f.Low, f.Close) +} + +// checkTimeInAscendingOrder checks if the times in a FundCandlesResponse are in ascending order. +// +// # Returns +// +// - An error if the times are not in ascending order. +func (f *FundCandlesResponse) checkTimeInAscendingOrder() error { + for i := 1; i < len(f.Date); i++ { + if f.Date[i] < f.Date[i-1] { + return fmt.Errorf("time is not in ascending order") + } + } + return nil +} + +// IsValid determines the validity of a FundCandlesResponse. It is primarily used to ensure that the data within the response adheres to expected formats and logical constraints before further processing or analysis. +// +// # Returns +// +// - bool: Indicates whether the FundCandlesResponse is valid. +// +// # Notes +// +// - This method should be used to prevent the propagation of invalid or corrupt data within applications that rely on mutual fund candle information. +func (f *FundCandlesResponse) IsValid() bool { + if err := f.Validate(); err != nil { + return false + } + return true +} + +// Validate checks the integrity and consistency of a FundCandlesResponse. It ensures that the data within the response adheres to expected formats and logical constraints, such as time being in ascending order and all data slices being of equal length. This method is crucial for preventing the propagation of invalid or corrupt data within an application that relies on mutual fund candle information. +// +// # Returns +// +// - error: An error if the FundCandlesResponse is not valid. A nil error indicates a valid FundCandlesResponse. +// +// # Notes +// +// - This method performs multiple checks in parallel to efficiently validate the response. +func (f *FundCandlesResponse) Validate() error { + // Create a channel to handle errors + errChan := make(chan error, 3) + + // Run each validation function concurrently + go func() { errChan <- f.checkTimeInAscendingOrder() }() + go func() { errChan <- f.checkForEqualSlices() }() + go func() { errChan <- f.checkForEmptySlices() }() + + // Check for errors from the validation functions + for i := 0; i < 3; i++ { + if err := <-errChan; err != nil { + return err + } + } + + return nil +} + +// checkForEqualSlices checks if all slices in a FundCandlesResponse have the same length. +// +// # Returns +// +// - An error if the slices have different lengths. +func (f *FundCandlesResponse) checkForEqualSlices() error { + // Create a slice of the lengths of the Date, Open, High, Low, and Close slices + lengths := []int{ + len(f.Date), + len(f.Open), + len(f.High), + len(f.Low), + len(f.Close), + } + + // Check if all lengths in the lengths slice are equal + // If they are not, return an error + for i := 1; i < len(lengths); i++ { + if lengths[i] != lengths[0] { + return fmt.Errorf("arrays are not of the same length") + } + } + + // If all lengths are equal, return nil + return nil +} + +// checkForEmptySlices checks if any of the slices in a FundCandlesResponse are empty. +// +// # Returns +// +// - An error if any of the slices are empty. +func (f *FundCandlesResponse) checkForEmptySlices() error { + // Check if any of the slices are empty + if len(f.Date) == 0 || len(f.Open) == 0 || len(f.High) == 0 || len(f.Low) == 0 || len(f.Close) == 0 { + return fmt.Errorf("one or more slices are empty") + } + + // If none of the slices are empty, return nil + return nil +} + +// MarshalJSON converts a FundCandlesResponse instance into its JSON representation. +// This method is primarily used for encoding the FundCandlesResponse into a JSON format that can be easily transmitted or stored. +// It organizes the mutual fund candle data into a structured JSON format, ensuring compatibility with systems that consume JSON. +// +// # Returns +// +// - []byte: The JSON-encoded representation of the FundCandlesResponse. +// - error: An error object that will be non-nil if the marshaling process encounters any issues. +func (f *FundCandlesResponse) MarshalJSON() ([]byte, error) { + // Create a new ordered map + o := orderedmap.New() + + // Set the "s" key to "ok" + o.Set("s", "ok") + + // Set the "t", "o", "h", "l", "c" keys to the corresponding slices in the struct + o.Set("t", f.Date) + o.Set("o", f.Open) + o.Set("h", f.High) + o.Set("l", f.Low) + o.Set("c", f.Close) + + // Marshal the ordered map into a JSON object and return the result + return json.Marshal(o) +} + +// UnmarshalJSON converts JSON data into a FundCandlesResponse instance. This method is essential for decoding JSON data received from external sources into a structured FundCandlesResponse object. It facilitates the easy consumption of JSON data by converting it into a more manageable Go struct. Additionally, it performs validation on the unmarshalled data to ensure it meets the expected format and constraints of a FundCandlesResponse. +// +// # Parameters +// +// - []byte: The JSON-encoded data that needs to be converted into a FundCandlesResponse. +// +// # Returns +// +// - error: An error object that will be non-nil if the unmarshaling process encounters any issues or if the validation of the unmarshalled data fails. +// +// # Notes +// +// - This method leverages an auxiliary struct to prevent infinite recursion during the unmarshalling process. +func (f *FundCandlesResponse) UnmarshalJSON(data []byte) error { + // Define a secondary type to prevent infinite recursion + type Alias FundCandlesResponse + aux := &struct { + *Alias + }{ + Alias: (*Alias)(f), + } + + // Unmarshal the data into our auxiliary struct + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Call the Validate method + if err := f.Validate(); err != nil { + // Print the contents of the auxiliary struct only if validation fails + fmt.Println(f.String()) + return err + } + + // Return nil if everything went well + return nil +} + +// GetDateRange calculates and returns the date range covered by the FundCandlesResponse. This method is useful for determining the span of time that the mutual fund candle data encompasses, allowing users to understand the temporal scope of the data they are working with. +// +// # Returns +// +// - dates.DateRange: The range of dates covered by the FundCandlesResponse. +// - error: An error if calculating the date range fails. +// +// # Notes +// +// - This method is particularly useful when filtering data based on specific time frames. +func (f *FundCandlesResponse) GetDateRange() (dates.DateRange, error) { + // Pass the slice of timestamps directly to Earliest and Latest + min, err1 := dates.Earliest(f.Date) + max, err2 := dates.Latest(f.Date) + if err1 != nil || err2 != nil { + return dates.DateRange{}, fmt.Errorf("error calculating date ranges: %v, %v", err1, err2) + } + + // Use NewDateRange to create a new DateRange instance + dr, err := dates.NewDateRange(min, max) + if err != nil { + return dates.DateRange{}, err + } + + return *dr, nil +} + +// pruneIndices removes data points at specified indices from a FundCandlesResponse. +// +// # Parameters +// +// - indices: A variadic list of integers specifying the indices of data points to remove. +func (f *FundCandlesResponse) pruneIndices(indices ...int) { + sort.Sort(sort.Reverse(sort.IntSlice(indices))) + for _, index := range indices { + if index < 0 || index >= len(f.Date) { + continue + } + f.Date = append(f.Date[:index], f.Date[index+1:]...) + f.Open = append(f.Open[:index], f.Open[index+1:]...) + f.High = append(f.High[:index], f.High[index+1:]...) + f.Low = append(f.Low[:index], f.Low[index+1:]...) + f.Close = append(f.Close[:index], f.Close[index+1:]...) + } +} + +// pruneBeforeIndex removes data points before a specified index from a FundCandlesResponse. +// +// # Parameters +// +// - index: The index before which all data points will be removed. +func (m *FundCandlesResponse) pruneBeforeIndex(index int) { + if index+1 < len(m.Date) { + m.Date = m.Date[index+1:] + m.Open = m.Open[index+1:] + m.High = m.High[index+1:] + m.Low = m.Low[index+1:] + m.Close = m.Close[index+1:] + } +} + +// pruneAfterIndex removes data points after a specified index from a FundCandlesResponse. +// +// # Parameters +// +// - index: The index after which all data points will be removed. +// +// # Returns +// +// - An error if the index is out of range. +func (m *FundCandlesResponse) pruneAfterIndex(index int) error { + // Check if the index is within the range of the slices + if index < 0 || index >= len(m.Date) { + return fmt.Errorf("index %d out of range (0-%d)", index, len(m.Date)-1) + } + + // Prune the Date, Open, High, Low, Close slices + m.Date = m.Date[:index] + m.Open = m.Open[:index] + m.High = m.High[:index] + m.Low = m.Low[:index] + m.Close = m.Close[:index] + + return nil +} + +// PruneOutsideDateRange method is used to filter out data points from a FundCandlesResponse that fall outside a specified date range. +// This method is essential when the user needs to focus on analyzing fund candle data within a specific period, +// thereby excluding irrelevant data points that do not fall within the desired date range. +// +// # Parameters +// +// - dr dates.DateRange: A struct specifying the start and end dates for the range within which data points should be retained. +// +// # Returns +// +// - error: An error if pruning fails, otherwise nil. +// +// # Notes +// +// - This method modifies the FundCandlesResponse in place, removing any data points that are outside the specified date range. +func (f *FundCandlesResponse) PruneOutsideDateRange(dr dates.DateRange) error { + // Validate all timestamps + validTimestamps, invalidTimestamps := dr.ValidateTimestamps(f.Date...) + fmt.Println("Valid timestamps: ", validTimestamps) + fmt.Println("Invalid timestamps: ", invalidTimestamps) + + // Loop through invalid timestamps, get index and prune + for _, invalidTimestamp := range invalidTimestamps { + for { + index := f.getIndex(invalidTimestamp) + if index >= len(f.Date) || f.Date[index] != invalidTimestamp { + break + } + f.pruneIndex(index) + } + } + + return nil +} + +// getIndex is a method on the FundCandlesResponse struct that searches for a given timestamp within the Date slice. +// +// # Parameters +// +// - t int64: The timestamp to search for within the Date slice. +// +// # Returns +// +// - int: The index of the first occurrence of the provided timestamp within the Date slice. +// If the timestamp is not found, it returns the length of the Date slice. +func (f *FundCandlesResponse) getIndex(t int64) int { + for i, timestamp := range f.Date { + if timestamp == t { + return i + } + } + return len(f.Date) +} + +// pruneIndex removes the element at the specified index from all slices within the FundCandlesResponse struct. +// +// # Parameters +// +// - index int: The index of the element to remove from each slice. +// +// # Returns +// +// - error: An error if the index is out of range. Otherwise, returns nil. +func (f *FundCandlesResponse) pruneIndex(index int) error { + if index < 0 || index >= len(f.Date) { + return fmt.Errorf("index %d out of range (0-%d)", index, len(f.Date)-1) + } + + // Remove the element at the index from the Date, Open, High, Low, and Close slices + f.Date = append(f.Date[:index], f.Date[index+1:]...) + f.Open = append(f.Open[:index], f.Open[index+1:]...) + f.High = append(f.High[:index], f.High[index+1:]...) + f.Low = append(f.Low[:index], f.Low[index+1:]...) + f.Close = append(f.Close[:index], f.Close[index+1:]...) + + return nil +} + +// CombineFundCandles merges two FundCandlesResponse structs into a single one. +// It checks if the versions of the two structs are the same, ensures there is no time overlap between them, +// and then combines their data into a new FundCandlesResponse struct. If the versions do not match or there is a time overlap, +// it returns an error. +// +// # Parameters +// +// - *FundCandlesResponse: The first FundCandlesResponse struct to be combined. +// - *FundCandlesResponse: The second FundCandlesResponse struct to be combined. +// +// # Returns +// +// - *FundCandlesResponse: A pointer to the newly combined FundCandlesResponse struct. +// - error: An error if the versions do not match, there is a time overlap, or the combined struct fails validation. +func CombineFundCandles(f1, f2 *FundCandlesResponse) (*FundCandlesResponse, error) { + // Check for time overlap using the DoesNotContain method + f1DateRange, err1 := f1.GetDateRange() + if err1 != nil { + return nil, fmt.Errorf("error getting date range from f1: %v", err1) + } + f2DateRange, err2 := f2.GetDateRange() + if err2 != nil { + return nil, fmt.Errorf("error getting date range from f2: %v", err2) + } + if !f1DateRange.DoesNotContain(f2DateRange) && !f2DateRange.DoesNotContain(f1DateRange) { + return nil, fmt.Errorf("time ranges overlap: f1 range %s, f2 range %s", f1DateRange.String(), f2DateRange.String()) + } + + // Combine the structs + combined := &FundCandlesResponse{ + Date: append(f1.Date, f2.Date...), + Open: append(f1.Open, f2.Open...), + High: append(f1.High, f2.High...), + Low: append(f1.Low, f2.Low...), + Close: append(f1.Close, f2.Close...), + } + + // Validate the combined struct + if err := combined.Validate(); err != nil { + return nil, fmt.Errorf("combineFundCandles validation failed: %v", err) + } + + // Dereference the old structs to free memory + f1 = nil + f2 = nil + + return combined, nil +} diff --git a/models/funds_candles_test.go b/models/funds_candles_test.go new file mode 100644 index 0000000..0de73fb --- /dev/null +++ b/models/funds_candles_test.go @@ -0,0 +1,356 @@ +package models + +import ( + "reflect" + "testing" + "time" + + "github.com/MarketDataApp/sdk-go/helpers/dates" +) + +var ( + jsonData1 = `{"s":"ok","t":[1672808400,1672894800],"o":[355.43,351.35],"h":[355.43,351.35],"l":[355.43,351.35],"c":[355.43,351.35]}` + jsonData2 = `{"s":"ok","t":[1704344400,1704430800],"o":[432.65,433.44],"h":[432.65,433.44],"l":[432.65,433.44],"c":[432.65,433.44]}` +) + +func TestFundUnpack(t *testing.T) { + testCases := []struct { + name string + jsonData string + expected []Candle + }{ + { + name: "jsonData1", + jsonData: jsonData1, + expected: []Candle{ + { + Date: time.Unix(1672808400, 0), + Open: 355.43, + High: 355.43, + Low: 355.43, + Close: 355.43, + }, + { + Date: time.Unix(1672894800, 0), + Open: 351.35, + High: 351.35, + Low: 351.35, + Close: 351.35, + }, + }, + }, + { + name: "jsonData2", + jsonData: jsonData2, + expected: []Candle{ + { + Date: time.Unix(1704344400, 0), + Open: 432.65, + High: 432.65, + Low: 432.65, + Close: 432.65, + }, + { + Date: time.Unix(1704430800, 0), + Open: 433.44, + High: 433.44, + Low: 433.44, + Close: 433.44, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Unmarshal the JSON data into a FundCandlesResponse instance + f := &FundCandlesResponse{} + err := f.UnmarshalJSON([]byte(tc.jsonData)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON data: %v", err) + } + + // Call the Unpack method + result, err := f.Unpack() + if err != nil { + t.Fatalf("Failed to unpack FundCandlesResponse: %v", err) + } + + // Check if the results match the expected results + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("Unpack did not return the expected result in %s. Got: %+v, want: %+v", tc.name, result, tc.expected) + } + }) + } +} + +func TestFundPruneAtIndex(t *testing.T) { + // Test cases + testCases := []struct { + jsonData string + index int + expected *FundCandlesResponse + }{ + { + jsonData: jsonData1, + index: 1, + expected: &FundCandlesResponse{ + Date: []int64{1672808400}, + Open: []float64{355.43}, + High: []float64{355.43}, + Low: []float64{355.43}, + Close: []float64{355.43}, + }, + }, + { + jsonData: jsonData2, + index: 1, + expected: &FundCandlesResponse{ + Date: []int64{1704344400}, + Open: []float64{432.65}, + High: []float64{432.65}, + Low: []float64{432.65}, + Close: []float64{432.65}, + }, + }, + } + + for _, tc := range testCases { + // Unmarshal the JSON data into a StockCandlesResponse instance + s := &FundCandlesResponse{} + err := s.UnmarshalJSON([]byte(tc.jsonData)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON data: %v", err) + } + + // Call the method with the specified index + s.pruneIndices(tc.index) + + // Get the DateRange from the StockCandlesResponse instance + dateRange, err := s.GetDateRange() + if err != nil { + t.Fatalf("Failed to get DateRange: %v", err) + } + + // Get the DateRange from the expected StockCandlesResponse struct + expectedDateRange, err := tc.expected.GetDateRange() + if err != nil { + t.Fatalf("Failed to get expected DateRange: %v", err) + } + + // Check if the results match the expected results + if !reflect.DeepEqual(dateRange, expectedDateRange) { + t.Errorf("PruneAtIndex did not prune correctly. Got: %+v, want: %+v", dateRange, expectedDateRange) + } + } +} + +func TestFundPruneBeforeIndex(t *testing.T) { + // Initialize a StockCandlesResponse instance + f := &FundCandlesResponse{ + Date: []int64{1, 2, 3, 4, 5}, + Open: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + High: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + Low: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + Close: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + } + + // Call the method with index 2 + f.pruneBeforeIndex(2) + + // Check the results + if len(f.Date) != 2 || f.Date[0] != 4 { + t.Errorf("Time was incorrect, got: %v, want: %v.", f.Date, []int64{4, 5}) + } + if len(f.Open) != 2 || f.Open[0] != 4.4 { + t.Errorf("Open was incorrect, got: %v, want: %v.", f.Open, []float64{4.4, 5.5}) + } +} + +func TestFundPruneAfterIndex(t *testing.T) { + // Initialize a StockCandlesResponse instance + f := &FundCandlesResponse{ + Date: []int64{1, 2, 3, 4, 5}, + Open: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + High: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + Low: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + Close: []float64{1.1, 2.2, 3.3, 4.4, 5.5}, + } + + // Call the method with index 2 + f.pruneAfterIndex(2) + + // Check the results + if len(f.Date) != 2 || f.Date[1] != 2 { + t.Errorf("Time was incorrect, got: %v, want: %v.", f.Date, []int64{1, 2}) + } + if len(f.Open) != 2 || f.Open[1] != 2.2 { + t.Errorf("Open was incorrect, got: %v, want: %v.", f.Open, []float64{1.1, 2.2}) + } + // Continue for the rest of the fields... +} + +func TestFundPruneOutsideDateRange(t *testing.T) { + // JSON data + data := []byte(`{ + "s": "ok", + "t": [946684800, 978307200, 1009843200, 1041379200, 1072915200], + "o": [1.1, 2.2, 3.3, 4.4, 5.5], + "h": [1.1, 2.2, 3.3, 4.4, 5.5], + "l": [1.1, 2.2, 3.3, 4.4, 5.5], + "c": [1.1, 2.2, 3.3, 4.4, 5.5] + }`) + + // Initialize a StockCandlesResponse instance and unmarshal the JSON data into it + s := &FundCandlesResponse{} + err := s.UnmarshalJSON(data) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + // Define a date range + dr, err := dates.NewDateRange(978307200, 1041379200) + if err != nil { + t.Fatalf("Failed to create DateRange: %v", err) + } + + // Call the method with the date range + err = s.PruneOutsideDateRange(*dr) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Check the results + if len(s.Date) != 3 || s.Date[0] != 978307200 || s.Date[2] != 1041379200 { + t.Errorf("Time was incorrect, got: %v, want: %v.", s.Date, []int64{978307200, 1009843200, 1041379200}) + } + if len(s.Open) != 3 || s.Open[0] != 2.2 || s.Open[2] != 4.4 { + t.Errorf("Open was incorrect, got: %v, want: %v.", s.Open, []float64{2.2, 3.3, 4.4}) + } + // Continue for the rest of the fields... +} + +func TestFundUnmarshalJSON(t *testing.T) { + // Define a JSON string that represents a StockCandlesResponse object + jsonStr := `{ + "s": "ok", + "t": [1699462680,1699462740,1699462800,1699462860,1699462920,1699462980,1699463040,1699463100,1699463160,1699463220], + "o": [182.09,182.04,182.02,181.94,181.9698,181.95,181.9197,181.94,181.9499,181.9703], + "h": [182.095,182.07,182.07,181.9799,182.04,181.97,182.02,182,182.07,181.9999], + "l": [182.01,182,181.89,181.855,181.915,181.87,181.91,181.9101,181.89,181.68], + "c": [182.03,182.01,181.947,181.9699,181.9592,181.9203,181.93,181.94,181.98,181.695] + }` + + // Create a new StockCandlesResponse object + sc := &FundCandlesResponse{} + + // Unmarshal the JSON into the StockCandlesResponse object + err := sc.UnmarshalJSON([]byte(jsonStr)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + // Check if the DateRange is set correctly + minTime := time.Unix(1699462680, 0) + maxTime := time.Unix(1699463220, 0) + expectedDateRange := dates.DateRange{StartDate: minTime, EndDate: maxTime} + + dateRange, err := sc.GetDateRange() + if err != nil { + t.Fatalf("Failed to get DateRange: %v", err) + } + if dateRange != expectedDateRange { + t.Errorf("DateRange was incorrect, got: %v, want: %v.", dateRange, expectedDateRange) + } +} + +func TestFundMarshalJSON(t *testing.T) { + // Initialize a StockCandlesResponse instance + f := &FundCandlesResponse{ + Date: []int64{1672808400, 1672894800}, + Open: []float64{355.43, 351.35}, + High: []float64{355.43, 351.35}, + Low: []float64{355.43, 351.35}, + Close: []float64{355.43, 351.35}, + } + // Call the method + data, err := f.MarshalJSON() + if err != nil { + t.Fatalf("Failed to marshal JSON: %v", err) + } + + // Check the results + expected := jsonData1 + if string(data) != expected { + t.Errorf("MarshalJSON did not return the expected result. Got: %s, want: %s", string(data), expected) + } +} + +func TestFundCombineStockCandlesResponse(t *testing.T) { + // Test cases + testCases := []struct { + name string + jsonData1 string + jsonData2 string + shouldErr bool + }{ + { + name: "Non-overlapping time ranges VFINX", + jsonData1: jsonData1, + jsonData2: jsonData2, + shouldErr: false, + }, + } + + for _, tc := range testCases { + // Unmarshal the JSON data into StockCandlesResponse instances + f1 := &FundCandlesResponse{} + err := f1.UnmarshalJSON([]byte(tc.jsonData1)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON data: %v", err) + } + + f2 := &FundCandlesResponse{} + err = f2.UnmarshalJSON([]byte(tc.jsonData2)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON data: %v", err) + } + + // Try to combine the StockCandlesResponse instances + _, err = CombineFundCandles(f1, f2) + if (err != nil) != tc.shouldErr { + t.Errorf("Test case %s: CombineStockCandlesResponse() error = %v, wantErr %v", tc.name, err, tc.shouldErr) + } + } +} + +func TestFundCheckTimeInAscendingOrder(t *testing.T) { + // Test cases + testCases := []struct { + date []int64 + shouldErr bool + }{ + { + date: []int64{1, 2, 3, 4, 5}, + shouldErr: false, // Time is in ascending order + }, + { + date: []int64{1, 3, 2, 4, 5}, + shouldErr: true, // Time is not in ascending order + }, + } + + for _, tc := range testCases { + // Create a FundCandlesResponse instance with the specified time + f := &FundCandlesResponse{ + Date: tc.date, + } + + // Call the method + err := f.checkTimeInAscendingOrder() + + // Check if the result matches the expected result + if (err != nil) != tc.shouldErr { + t.Errorf("checkTimeInAscendingOrder() error = %v, wantErr %v", err, tc.shouldErr) + } + } +} diff --git a/models/options_quotes.go b/models/options_quotes.go index c082612..aac45d1 100644 --- a/models/options_quotes.go +++ b/models/options_quotes.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "strings" "time" "github.com/MarketDataApp/sdk-go/helpers/dates" @@ -335,6 +336,10 @@ func (oqr *OptionQuotesResponse) String() string { // formatFloat64Slice is a helper function to format slices of *float64 for printing. func formatFloat64Slice(slice []*float64) string { + if len(slice) == 0 { + return "[nil]" + } + var result []string for _, ptr := range slice { if ptr != nil { @@ -343,7 +348,7 @@ func formatFloat64Slice(slice []*float64) string { result = append(result, "nil") } } - return fmt.Sprintf("%v", result) + return fmt.Sprintf("[%s]", strings.Join(result, ", ")) } // nilIfEmpty checks if the slice is nil or empty and returns nil for the current index if so. diff --git a/options_chain_test.go b/options_chain_test.go index 571c7e6..2828e1c 100644 --- a/options_chain_test.go +++ b/options_chain_test.go @@ -33,7 +33,7 @@ func ExampleOptionChainRequest_packed() { 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]} + // 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: [nil], Theta: [nil], Vega: [nil], Rho: [nil], IntrinsicValue: [32.01], ExtrinsicValue: [0.03]} } @@ -46,6 +46,6 @@ func ExampleOptionChainRequest_get() { 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} + // 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: 74, 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: 74, 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_quotes_test.go b/options_quotes_test.go index 4e64e30..9ef322b 100644 --- a/options_quotes_test.go +++ b/options_quotes_test.go @@ -23,6 +23,16 @@ func ExampleOptionQuoteRequest_packed() { } fmt.Println(resp) - // Output: OptionQuotesResponse{OptionSymbol: ["AAPL250117P00150000"], Underlying: ["AAPL"], Expiration: [1737147600], Side: ["put"], Strike: [150], FirstTraded: [1662989400], 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]} + // Output: OptionQuotesResponse{OptionSymbol: ["AAPL250117P00150000"], Underlying: ["AAPL"], Expiration: [1737147600], Side: ["put"], Strike: [150], FirstTraded: [1662989400], 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: [nil], Delta: [nil], Gamma: [nil], Theta: [nil], Vega: [nil], Rho: [nil], IntrinsicValue: [0], ExtrinsicValue: [3.58]} } +func ExampleOptionQuoteRequest_raw() { + resp, err := OptionQuote().OptionSymbol("AAPL250117P00150000").Date("2024-02-05").Raw() + if err != nil { + fmt.Print(err) + return + } + + fmt.Println(resp) + // Output: {"s":"ok","optionSymbol":["AAPL250117P00150000"],"underlying":["AAPL"],"expiration":[1737147600],"side":["put"],"strike":[150.0],"firstTraded":[1662989400],"dte":[347],"updated":[1707166800],"bid":[3.5],"bidSize":[634],"mid":[3.58],"ask":[3.65],"askSize":[292],"last":[3.55],"openInterest":[18027],"volume":[44],"inTheMoney":[false],"intrinsicValue":[0.0],"extrinsicValue":[3.58],"underlyingPrice":[187.68],"iv":[null],"delta":[null],"gamma":[null],"theta":[null],"vega":[null],"rho":[null]} +} diff --git a/stocks_candles_test.go b/stocks_candles_test.go index 9150a1b..31b6610 100644 --- a/stocks_candles_test.go +++ b/stocks_candles_test.go @@ -10,7 +10,7 @@ func ExampleStockCandlesRequest_raw() { } fmt.Println(scr) - // Output: {"s":"ok","t":[1672756200,1672770600,1672842600,1672857000],"o":[130.28,124.6699,126.89,127.265],"h":[130.9,125.42,128.6557,127.87],"l":[124.19,124.17,125.08,125.28],"c":[124.6499,125.05,127.2601,126.38],"v":[64411753,30727802,49976607,28870878]} + // Output: {"s":"ok","t":[1672756200,1672770600,1672842600,1672857000],"o":[130.28,124.67,126.89,127.26],"h":[130.9,125.42,128.66,127.87],"l":[124.19,124.17,125.08,125.28],"c":[124.65,125.05,127.26,126.38],"v":[64192007,30727802,49096197,28870578]} } func ExampleStockCandlesRequest_packed() { @@ -21,7 +21,7 @@ func ExampleStockCandlesRequest_packed() { } fmt.Println(scr) - // Output: StockCandlesResponse{Date: [1672756200 1672770600 1672842600 1672857000], Open: [130.28 124.6699 126.89 127.265], High: [130.9 125.42 128.6557 127.87], Low: [124.19 124.17 125.08 125.28], Close: [124.6499 125.05 127.2601 126.38], Volume: [64411753 30727802 49976607 28870878]} + // Output: StockCandlesResponse{Date: [1672756200 1672770600 1672842600 1672857000], Open: [130.28 124.67 126.89 127.26], High: [130.9 125.42 128.66 127.87], Low: [124.19 124.17 125.08 125.28], Close: [124.65 125.05 127.26 126.38], Volume: [64192007 30727802 49096197 28870578]} } func ExampleStockCandlesRequest_get() { @@ -34,9 +34,8 @@ func ExampleStockCandlesRequest_get() { for _, candle := range scr { fmt.Println(candle) } - // Output: Candle{Time: 2023-01-03 09:30:00 -05:00, Open: 130.28, High: 130.9, Low: 124.19, Close: 124.6499, Volume: 64411753} - // Candle{Time: 2023-01-03 13:30:00 -05:00, Open: 124.6699, High: 125.42, Low: 124.17, Close: 125.05, Volume: 30727802} - // Candle{Time: 2023-01-04 09:30:00 -05:00, Open: 126.89, High: 128.6557, Low: 125.08, Close: 127.2601, Volume: 49976607} - // Candle{Time: 2023-01-04 13:30:00 -05:00, Open: 127.265, High: 127.87, Low: 125.28, Close: 126.38, Volume: 28870878} - + // Output: Candle{Time: 2023-01-03 09:30:00 -05:00, Open: 130.28, High: 130.9, Low: 124.19, Close: 124.65, Volume: 64192007} + // Candle{Time: 2023-01-03 13:30:00 -05:00, Open: 124.67, High: 125.42, Low: 124.17, Close: 125.05, Volume: 30727802} + // Candle{Time: 2023-01-04 09:30:00 -05:00, Open: 126.89, High: 128.66, Low: 125.08, Close: 127.26, Volume: 49096197} + // Candle{Time: 2023-01-04 13:30:00 -05:00, Open: 127.26, High: 127.87, Low: 125.28, Close: 126.38, Volume: 28870578} } \ No newline at end of file diff --git a/stocks_candles_v2_test.go b/stocks_candles_v2_test.go index 2a6bd33..48e09c8 100644 --- a/stocks_candles_v2_test.go +++ b/stocks_candles_v2_test.go @@ -14,24 +14,24 @@ func ExampleStockCandlesRequestV2_get() { fmt.Println(candle) } // Output: Candle{Date: 2023-01-03, Open: 130.28, High: 130.9, Low: 124.17, Close: 125.07, Volume: 112117471, VWAP: 125.725, N: 1021065} - // Candle{Date: 2023-01-04, Open: 126.89, High: 128.6557, Low: 125.08, Close: 126.36, Volume: 89100633, VWAP: 126.6464, N: 770042} + // Candle{Date: 2023-01-04, Open: 126.89, High: 128.66, Low: 125.08, Close: 126.36, Volume: 89100633, VWAP: 126.6464, N: 770042} // Candle{Date: 2023-01-05, Open: 127.13, High: 127.77, Low: 124.76, Close: 125.02, Volume: 80716808, VWAP: 126.0883, N: 665458} // Candle{Date: 2023-01-06, Open: 126.01, High: 130.29, Low: 124.89, Close: 129.62, Volume: 87754715, VWAP: 128.1982, N: 711520} - // Candle{Date: 2023-01-09, Open: 130.465, High: 133.41, Low: 129.89, Close: 130.15, Volume: 70790813, VWAP: 131.6292, N: 645365} - // Candle{Date: 2023-01-10, Open: 130.26, High: 131.2636, Low: 128.12, Close: 130.73, Volume: 63896155, VWAP: 129.822, N: 554940} + // Candle{Date: 2023-01-09, Open: 130.46, High: 133.41, Low: 129.89, Close: 130.15, Volume: 70790813, VWAP: 131.6292, N: 645365} + // Candle{Date: 2023-01-10, Open: 130.26, High: 131.26, Low: 128.12, Close: 130.73, Volume: 63896155, VWAP: 129.822, N: 554940} // Candle{Date: 2023-01-11, Open: 131.25, High: 133.51, Low: 130.46, Close: 133.49, Volume: 69458949, VWAP: 132.3081, N: 561278} // Candle{Date: 2023-01-12, Open: 133.88, High: 134.26, Low: 131.44, Close: 133.41, Volume: 71379648, VWAP: 133.171, N: 635331} // Candle{Date: 2023-01-13, Open: 132.03, High: 134.92, Low: 131.66, Close: 134.76, Volume: 57809719, VWAP: 133.6773, N: 537385} // Candle{Date: 2023-01-17, Open: 134.83, High: 137.29, Low: 134.13, Close: 135.94, Volume: 63612627, VWAP: 135.7587, N: 595831} - // Candle{Date: 2023-01-18, Open: 136.815, High: 138.61, Low: 135.03, Close: 135.21, Volume: 69672800, VWAP: 136.3316, N: 578304} + // Candle{Date: 2023-01-18, Open: 136.82, High: 138.61, Low: 135.03, Close: 135.21, Volume: 69672800, VWAP: 136.3316, N: 578304} // Candle{Date: 2023-01-19, Open: 134.08, High: 136.25, Low: 133.77, Close: 135.27, Volume: 58280413, VWAP: 134.9653, N: 491674} // Candle{Date: 2023-01-20, Open: 135.28, High: 138.02, Low: 134.22, Close: 137.87, Volume: 80200655, VWAP: 136.3762, N: 552230} - // Candle{Date: 2023-01-23, Open: 138.12, High: 143.315, Low: 137.9, Close: 141.11, Volume: 81760313, VWAP: 141.2116, N: 719288} - // Candle{Date: 2023-01-24, Open: 140.305, High: 143.16, Low: 140.3, Close: 142.53, Volume: 66435142, VWAP: 142.0507, N: 498679} + // Candle{Date: 2023-01-23, Open: 138.12, High: 143.32, Low: 137.9, Close: 141.11, Volume: 81760313, VWAP: 141.2116, N: 719288} + // Candle{Date: 2023-01-24, Open: 140.3, High: 143.16, Low: 140.3, Close: 142.53, Volume: 66435142, VWAP: 142.0507, N: 498679} // Candle{Date: 2023-01-25, Open: 140.89, High: 142.43, Low: 138.81, Close: 141.86, Volume: 65799349, VWAP: 140.7526, N: 536505} // Candle{Date: 2023-01-26, Open: 143.17, High: 144.25, Low: 141.9, Close: 143.96, Volume: 54105068, VWAP: 143.3429, N: 472135} - // Candle{Date: 2023-01-27, Open: 143.155, High: 147.23, Low: 143.08, Close: 145.93, Volume: 70547743, VWAP: 145.8365, N: 560022} - // Candle{Date: 2023-01-30, Open: 144.955, High: 145.55, Low: 142.85, Close: 143, Volume: 64015274, VWAP: 143.6524, N: 551111} + // Candle{Date: 2023-01-27, Open: 143.16, High: 147.23, Low: 143.08, Close: 145.93, Volume: 70547743, VWAP: 145.8365, N: 560022} + // Candle{Date: 2023-01-30, Open: 144.96, High: 145.55, Low: 142.85, Close: 143, Volume: 64015274, VWAP: 143.6524, N: 551111} // Candle{Date: 2023-01-31, Open: 142.7, High: 144.34, Low: 142.28, Close: 144.29, Volume: 65874459, VWAP: 143.6473, N: 468170} } @@ -44,5 +44,5 @@ func ExampleStockCandlesRequestV2_packed() { } fmt.Println(scr2) - // Output: StockCandlesResponse{Date: [1672722000 1672808400 1672894800 1672981200 1673240400 1673326800 1673413200 1673499600 1673586000 1673931600 1674018000 1674104400 1674190800 1674450000 1674536400 1674622800 1674709200 1674795600 1675054800 1675141200], Open: [130.28 126.89 127.13 126.01 130.465 130.26 131.25 133.88 132.03 134.83 136.815 134.08 135.28 138.12 140.305 140.89 143.17 143.155 144.955 142.7], High: [130.9 128.6557 127.77 130.29 133.41 131.2636 133.51 134.26 134.92 137.29 138.61 136.25 138.02 143.315 143.16 142.43 144.25 147.23 145.55 144.34], Low: [124.17 125.08 124.76 124.89 129.89 128.12 130.46 131.44 131.66 134.13 135.03 133.77 134.22 137.9 140.3 138.81 141.9 143.08 142.85 142.28], Close: [125.07 126.36 125.02 129.62 130.15 130.73 133.49 133.41 134.76 135.94 135.21 135.27 137.87 141.11 142.53 141.86 143.96 145.93 143 144.29], Volume: [112117471 89100633 80716808 87754715 70790813 63896155 69458949 71379648 57809719 63612627 69672800 58280413 80200655 81760313 66435142 65799349 54105068 70547743 64015274 65874459], VWAP: [125.725 126.6464 126.0883 128.1982 131.6292 129.822 132.3081 133.171 133.6773 135.7587 136.3316 134.9653 136.3762 141.2116 142.0507 140.7526 143.3429 145.8365 143.6524 143.6473], N: [1021065 770042 665458 711520 645365 554940 561278 635331 537385 595831 578304 491674 552230 719288 498679 536505 472135 560022 551111 468170]} + // Output: StockCandlesResponse{Date: [1672722000 1672808400 1672894800 1672981200 1673240400 1673326800 1673413200 1673499600 1673586000 1673931600 1674018000 1674104400 1674190800 1674450000 1674536400 1674622800 1674709200 1674795600 1675054800 1675141200], Open: [130.28 126.89 127.13 126.01 130.46 130.26 131.25 133.88 132.03 134.83 136.82 134.08 135.28 138.12 140.3 140.89 143.17 143.16 144.96 142.7], High: [130.9 128.66 127.77 130.29 133.41 131.26 133.51 134.26 134.92 137.29 138.61 136.25 138.02 143.32 143.16 142.43 144.25 147.23 145.55 144.34], Low: [124.17 125.08 124.76 124.89 129.89 128.12 130.46 131.44 131.66 134.13 135.03 133.77 134.22 137.9 140.3 138.81 141.9 143.08 142.85 142.28], Close: [125.07 126.36 125.02 129.62 130.15 130.73 133.49 133.41 134.76 135.94 135.21 135.27 137.87 141.11 142.53 141.86 143.96 145.93 143 144.29], Volume: [112117471 89100633 80716808 87754715 70790813 63896155 69458949 71379648 57809719 63612627 69672800 58280413 80200655 81760313 66435142 65799349 54105068 70547743 64015274 65874459], VWAP: [125.725 126.6464 126.0883 128.1982 131.6292 129.822 132.3081 133.171 133.6773 135.7587 136.3316 134.9653 136.3762 141.2116 142.0507 140.7526 143.3429 145.8365 143.6524 143.6473], N: [1021065 770042 665458 711520 645365 554940 561278 635331 537385 595831 578304 491674 552230 719288 498679 536505 472135 560022 551111 468170]} }