diff --git a/market.go b/market.go index 1d4e6d9..c244004 100644 --- a/market.go +++ b/market.go @@ -132,8 +132,11 @@ func (o OHCLDataOpts) String() string { return v.Encode() } +type ohclData map[string]any + // OHCLData retrieves the last entry in the OHLC array is for the current, -// not-yet-committed frame and will always be present, regardless of the value of since +// not-yet-committed frame and will always be present, regardless of the value of since. +// Docs: https://docs.kraken.com/rest/#tag/Market-Data/operation/getOHLCData func (m *MarketData) OHCLData(ctx context.Context, opts OHCLDataOpts) (*OHCL, error) { if opts.Pair == "" { return nil, errors.New("pair is required") @@ -145,25 +148,27 @@ func (m *MarketData) OHCLData(ctx context.Context, opts OHCLDataOpts) (*OHCL, er return nil, err } - var v OHCLData + var v ohclData if err := m.client.do(req, &v); err != nil { return nil, err } var last int64 - if v, ok := v["last"].(float64); ok { - last = int64(v) + if l, ok := v["last"].(float64); ok { + last = int64(l) } - var pair TickData - if v, ok := v[string(opts.Pair)].([]interface{}); ok { - pair = TickData{v} + var ticks Ticks + if p, ok := v[string(opts.Pair)].([]any); ok { + for _, t := range p { + if pv, ok := t.([]any); ok { + ticks = append(ticks, pv) + } + } } - ohcl := &OHCL{ + return &OHCL{ Last: last, - Pair: pair, - } - - return ohcl, nil + Pair: ticks, + }, nil } diff --git a/market_test.go b/market_test.go index 9695032..442c665 100644 --- a/market_test.go +++ b/market_test.go @@ -297,8 +297,27 @@ func TestMarketData_OHCLData(t *testing.T) { }, want: &OHCL{ Last: 1688672160, - Pair: TickData{ - []any{}, + Pair: Ticks{ + { + float64(1688671200), + "30306.1", + "30306.2", + "30305.7", + "30305.7", + "30306.1", + "3.39243896", + float64(23), + }, + { + float64(1688671260), + "30304.5", + "30304.5", + "30300.0", + "30300.0", + "30300.0", + "4.42996871", + float64(18), + }, }, }, }, @@ -320,8 +339,8 @@ func TestMarketData_OHCLData(t *testing.T) { return } - if !reflect.DeepEqual(got.Last, tt.want.Last) { - t.Errorf("MarketData.OHCLData().Last = %v, want %v", got, tt.want) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarketData.OHCLData() = %v, want %v", got, tt.want) } }) } diff --git a/testdata/ohcl_data.json b/testdata/ohcl_data.json index e8994c7..f43bd92 100644 --- a/testdata/ohcl_data.json +++ b/testdata/ohcl_data.json @@ -21,26 +21,6 @@ "30300.0", "4.42996871", 18 - ], - [ - 1688671320, - "30300.3", - "30300.4", - "30291.4", - "30291.4", - "30294.7", - "2.13024789", - 25 - ], - [ - 1688671380, - "30291.8", - "30295.1", - "30291.8", - "30295.0", - "30293.8", - "1.01836275", - 9 ] ], "last": 1688672160 diff --git a/types.go b/types.go index a9b88ce..7eb5e46 100644 --- a/types.go +++ b/types.go @@ -136,11 +136,127 @@ type WebsocketsToken struct { Expires int64 `json:"expires"` } -type TickData [][]any +// Tick represents a tick. +type Tick []any -type OHCLData map[string]any +// TickValues represents the values of a tick. +type TickValues struct { + Open string + High string + Low string + Close string + Vwap string + Volume string + Count int64 + Time int64 +} + +// Valid returns true if the tick is valid. +func (t Tick) Valid() bool { + return len(t) == 8 +} + +// Time returns the time of the tick. +func (t Tick) Time() int64 { + v := t[0] + switch value := v.(type) { + case float64: + return int64(value) + case int64: + return value + case int: + return int64(value) + default: + return 0 + } +} + +// Open returns the open price of the tick. +func (t Tick) Open() string { + if v, ok := t[1].(string); ok { + return v + } + return "" +} + +// High returns the high price of the tick. +func (t Tick) High() string { + if v, ok := t[2].(string); ok { + return v + } + return "" +} + +// Low returns the low price of the tick. +func (t Tick) Low() string { + if v, ok := t[3].(string); ok { + return v + } + return "" +} + +// Close returns the close price of the tick. +func (t Tick) Close() string { + if v, ok := t[4].(string); ok { + return v + } + return "" +} + +// Vwap returns the vwap of the tick. +func (t Tick) Vwap() string { + if v, ok := t[5].(string); ok { + return v + } + return "" +} + +// Volume returns the volume of the tick. +func (t Tick) Volume() string { + if v, ok := t[6].(string); ok { + return v + } + return "" +} + +// Count returns the count of the tick. +func (t Tick) Count() int64 { + v := t[7] + switch value := v.(type) { + case float64: + return int64(value) + case int64: + return value + case int: + return int64(value) + default: + return 0 + } +} + +// Values returns the values of the tick. +func (t Tick) Values() TickValues { + if !t.Valid() { + return TickValues{} + } + + return TickValues{ + Open: t.Open(), + High: t.High(), + Low: t.Low(), + Close: t.Close(), + Vwap: t.Vwap(), + Volume: t.Volume(), + Count: t.Count(), + Time: t.Time(), + } +} + +// Ticks is a slice of Tick. +type Ticks []Tick +// OHCL represents the OHCL data. type OHCL struct { Last int64 - Pair TickData + Pair Ticks } diff --git a/types_test.go b/types_test.go new file mode 100644 index 0000000..aa310f6 --- /dev/null +++ b/types_test.go @@ -0,0 +1,53 @@ +package kraken + +import ( + "reflect" + "testing" +) + +func TestTick_Values(t *testing.T) { + tests := []struct { + name string + tr Tick + wantTick TickValues + }{ + { + name: "invalid tick", + tr: Tick{}, + }, + { + name: "tick with invalid values", + tr: Tick{"0", 1, 2, 3, 4, 5, 6, "7"}, + }, + { + name: "tick with valid values", + tr: Tick{ + 1688671200, + "30306.1", + "30306.2", + "30305.7", + "30305.7", + "30306.1", + "3.39243896", + 23, + }, + wantTick: TickValues{ + Time: 1688671200, + Open: "30306.1", + High: "30306.2", + Low: "30305.7", + Close: "30305.7", + Vwap: "30306.1", + Volume: "3.39243896", + Count: 23, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.tr.Values(); !reflect.DeepEqual(got, tt.wantTick) { + t.Errorf("tick = %v, want %v", got, tt.wantTick) + } + }) + } +}