Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GET /api/trades #2

Merged
merged 2 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ The coincheck package is a client for the API provided by Coincheck, Inc., which
- Private API: Requires authentication using the API Key and API Secret issued by the Coincheck service.

The coincheck package supports both Public and Private APIs.
- [Official API documentation](https://coincheck.com/documents/exchange/api)
- [Official API client](https://github.com/coincheckjp/coincheck-go)
- [Coincheck official API documentation](https://coincheck.com/documents/exchange/api)
- [Coincheck official API client](https://github.com/coincheckjp/coincheck-go)

## Supported OS and go version

Expand Down Expand Up @@ -78,7 +78,8 @@ If you want to execute the Private API, you need to create a client with the API

| API | Method Name |Description |
| :--- | :--- | :--- |
| GET /api/ticker | GetTicker() | Check latest ticker information |
| GET /api/ticker | GetTicker() | Check latest ticker information. |
| GET /api/trades | GetTrades() | You can get current order transactions. |

### Private API

Expand Down
20 changes: 20 additions & 0 deletions coincheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package coincheck

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -108,3 +110,21 @@ func (c *Client) createRequest(ctx context.Context, input createRequestInput) (*
req.Header.Add("cache-control", "no-cache")
return req, nil
}

// Do sends an HTTP request and returns an HTTP response.
func (c *Client) do(req *http.Request, output any) error {
resp, err := c.client.Do(req)
if err != nil {
return withPrefixError(err)
}
nao1215 marked this conversation as resolved.
Show resolved Hide resolved
defer resp.Body.Close() //nolint: errcheck // ignore error

if resp.StatusCode != http.StatusOK {
return withPrefixError(fmt.Errorf("unexpected status code=%d", resp.StatusCode))
}
nao1215 marked this conversation as resolved.
Show resolved Hide resolved

if err := json.NewDecoder(resp.Body).Decode(output); err != nil {
return withPrefixError(err)
}
nao1215 marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
11 changes: 11 additions & 0 deletions order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package coincheck

// OrderType represents the order type.
type OrderType string

const (
// OrderTypeBuy is the order type of buy.
OrderTypeBuy OrderType = "buy"
// OrderTypeSell is the order type of sell.
OrderTypeSell OrderType = "sell"
)
26 changes: 26 additions & 0 deletions pagenation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package coincheck

// Pagination represents the pagination of coincheck API.
// It is possible to get by dividing the data.
type Pagination struct {
// Limit is the number of data to get.
Limit int `json:"limit"`
// PaginationOrder is the order of the data. You can specify "desc" or "asc".
PaginationOrder PaginationOrder `json:"order"`
// StartingAfter is the ID of the data to start getting.
// Greater than the specified ID. For example, if you specify 3, you will get data from ID 4.
StartingAfter int `json:"starting_after,omitempty"`
// EndingBefore is the ID of the data to end getting.
// Less than the specified ID. For example, if you specify 3, you will get data up to ID 2.
EndingBefore int `json:"ending_before,omitempty"`
}
nao1215 marked this conversation as resolved.
Show resolved Hide resolved

// PaginationOrder represents the order of the pagination.
type PaginationOrder string

const (
// PaginationOrderDesc is the order of the pagination in descending order.
PaginationOrderDesc PaginationOrder = "desc"
// PaginationOrderAsc is the order of the pagination in ascending order.
PaginationOrderAsc PaginationOrder = "asc"
)
17 changes: 2 additions & 15 deletions ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package coincheck

import (
"context"
"encoding/json"
"fmt"
"net/http"
)

Expand Down Expand Up @@ -76,20 +74,9 @@ func (c *Client) GetTicker(ctx context.Context, input GetTickerInput) (*GetTicke
return nil, err
}

resp, err := c.client.Do(req)
if err != nil {
return nil, withPrefixError(err)
}
defer resp.Body.Close() //nolint: errcheck // ignore error

if resp.StatusCode != http.StatusOK {
fmt.Println(req.URL)
return nil, withPrefixError(fmt.Errorf("unexpected status code=%d", resp.StatusCode))
}

var output GetTickerResponse
if err := json.NewDecoder(resp.Body).Decode(&output); err != nil {
return nil, withPrefixError(err)
if err := c.do(req, &output); err != nil {
return nil, err
}
return &output, nil
}
60 changes: 60 additions & 0 deletions trade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package coincheck

import (
"context"
"net/http"
)

// GetTradesInput represents the input parameter for GetTrades
type GetTradesInput struct {
// Pair is the pair of the currency. e.g. btc_jpy.
Pair Pair
}

// GetTradesResponse represents the output from GetTrades
type GetTradesResponse struct {
// Success is a boolean value that indicates the success of the API call.
Success bool `json:"success"`
// Pagination is the pagination of the data.
Pagination Pagination `json:"pagination"`
// Data is a list of trades.
Data []Trade `json:"data"`
}

// Trade represents a trade.
type Trade struct {
// ID is the trade ID.
ID int `json:"id"`
// Amount is the amount of the trade.
Amount float64 `json:"amount"`
// Rate is the rate of the trade.
Rate float64 `json:"rate"`
// Pair is the pair of the currency.
Pair Pair `json:"pair"`
// OrderType is the order type.
OrderType OrderType `json:"order_type"`
// CreatedAt is the creation time of the trade.
CreatedAt string `json:"created_at"`
}

// GetTrades returns a list of trades (order transactions).
// API: GET /api/trades
// Visibility: Public
func (c *Client) GetTrades(ctx context.Context, input GetTradesInput) (*GetTradesResponse, error) {
req, err := c.createRequest(ctx, createRequestInput{
method: http.MethodGet,
path: "/api/trades",
queryParam: map[string]string{
"pair": string(input.Pair),
},
})
if err != nil {
return nil, err
}

var output GetTradesResponse
if err := c.do(req, &output); err != nil {
return nil, err
}
return &output, nil
}
125 changes: 125 additions & 0 deletions trade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package coincheck

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestClient_GetTrades(t *testing.T) {
t.Run("In the case of a successful GET /api/trades request", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wantMethod := http.MethodGet
if got := r.Method; got != wantMethod {
t.Errorf("Method: got %v, want %v", got, wantMethod)
}

wantEndpoint := "/api/trades"
if got := r.URL.Path; got != wantEndpoint {
t.Errorf("Endpoint: got %v, want %v", got, wantEndpoint)
}

wantPair := PairETCJPY
if got := r.URL.Query().Get("pair"); got != wantPair.String() {
t.Errorf("Pair: got %v, want %v", got, wantPair)
}

result := GetTradesResponse{
Success: true,
Pagination: Pagination{
Limit: 1,
PaginationOrder: "desc",
StartingAfter: 0,
EndingBefore: 0,
},
Data: []Trade{
{
ID: 1,
Amount: 1,
Rate: 1000000,
Pair: PairETCJPY,
OrderType: OrderTypeBuy,
CreatedAt: "2021-01-01T00:00:00Z",
},
{
ID: 2,
Amount: 2,
Rate: 2000000,
Pair: PairETCJPY,
OrderType: OrderTypeSell,
CreatedAt: "2021-01-02T00:00:00Z",
},
},
}
if err := json.NewEncoder(w).Encode(result); err != nil {
t.Fatal(err)
}
}))

client, err := NewClient(WithBaseURL(testServer.URL))
if err != nil {
t.Fatal(err)
}

input := GetTradesInput{
Pair: PairETCJPY,
}
got, err := client.GetTrades(context.Background(), input)
if err != nil {
t.Fatal(err)
}

want := &GetTradesResponse{
Success: true,
Pagination: Pagination{
Limit: 1,
PaginationOrder: PaginationOrderDesc,
StartingAfter: 0,
EndingBefore: 0,
},
Data: []Trade{
{
ID: 1,
Amount: 1,
Rate: 1000000,
Pair: PairETCJPY,
OrderType: OrderTypeBuy,
CreatedAt: "2021-01-01T00:00:00Z",
},
{
ID: 2,
Amount: 2,
Rate: 2000000,
Pair: PairETCJPY,
OrderType: OrderTypeSell,
CreatedAt: "2021-01-02T00:00:00Z",
},
},
}
if diff := cmp.Diff(want, got); diff != "" {
printDiff(t, diff)
}
})

t.Run("In the case of a failed GET /api/trades request", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))

client, err := NewClient(WithBaseURL(testServer.URL))
if err != nil {
t.Fatal(err)
}

input := GetTradesInput{
Pair: PairETCJPY,
}
if _, err = client.GetTrades(context.Background(), input); err == nil {
t.Fatal("err must not be nil")
}
})
}
16 changes: 2 additions & 14 deletions withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package coincheck

import (
"context"
"encoding/json"
"fmt"
"net/http"
)

Expand Down Expand Up @@ -46,19 +44,9 @@ func (c *Client) GetBankAccounts(ctx context.Context) (*GetBankAccountsResponse,
return nil, err
}

resp, err := c.client.Do(req)
if err != nil {
return nil, withPrefixError(err)
}
defer resp.Body.Close() //nolint: errcheck // ignore error

if resp.StatusCode != http.StatusOK {
return nil, withPrefixError(fmt.Errorf("unexpected status code=%d", resp.StatusCode))
}

var output GetBankAccountsResponse
if err := json.NewDecoder(resp.Body).Decode(&output); err != nil {
return nil, withPrefixError(err)
if err := c.do(req, &output); err != nil {
return nil, err
}
return &output, nil
}