diff --git a/README.md b/README.md index a7d060c..c56ebde 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ If you want to execute the Private API, you need to create a client with the API | GET /api/ticker | [GetTicker()](https://pkg.go.dev/github.com/nao1215/coincheck#Client.GetTicker) | Check latest ticker information. | | GET /api/trades | [GetTrades()](https://pkg.go.dev/github.com/nao1215/coincheck#Client.GetTrades) | You can get current order transactions. | | GET /api/order_books | [GetOrderBooks()](https://pkg.go.dev/github.com/nao1215/coincheck#Client.GetOrderBooks) | Fetch order book information. | +| GET /api/exchange/orders/rate | [GetExchangeOrdersRate()](https://pkg.go.dev/github.com/nao1215/coincheck#Client.GetExchangeOrdersRate) | To calculate the rate from the order of the exchange. | | GET /api/rate/[pair] | [GetRate()](https://pkg.go.dev/github.com/nao1215/coincheck#Client.GetRate) | Get the Standard Rate of Coin. | ### Private API @@ -104,6 +105,9 @@ If you want to execute the Private API, you need to create a client with the API ## Contribution First off, thanks for taking the time to contribute! See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. Contributions are not only related to development. For example, GitHub Star motivates me to develop! Please feel free to contribute to this project. +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=nao1215/coincheck&type=Date)](https://star-history.com/#nao1215/coincheck&Date) ## Contributors ✨ @@ -135,3 +139,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +## Reasons for Creating the coincheck package + +I wanted to create a Bot that is reason why. Another reason is that I started creating it without much thought. Foolish. Now, I would rather have a bitFlyer Bot. + +I received a bonus and bought cryptocurrency out of curiosity (I bought it secretly from my spouse and have already lost 10,000 yen). I became interested in the mechanism of cryptocurrency itself, as well as in how to trade cryptocurrency automatically using a Bot. + +The cryptocurrency exchanges I use are Coincheck and bitFlyer, with the former being the one where I hold the most coins. Given this situation, it was natural for me to consider creating a Bot for Coincheck. + +However, the official Coincheck API client (Golang) was quite simplistic. I had no choice but to create my own. At this point, I realized my mistake. There was a lot of information that could not be gleaned from the official documentation. diff --git a/go.mod b/go.mod index 771023f..e5ede6a 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/nao1215/coincheck go 1.20 -require github.com/google/go-cmp v0.6.0 +require ( + github.com/google/go-cmp v0.6.0 + github.com/shogo82148/pointer v1.3.0 +) diff --git a/go.sum b/go.sum index 5a8d551..7cc21a1 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/shogo82148/pointer v1.3.0 h1:LW5V2jUAjFNjS8e7k/PgFoh3EavOSB/vvN85aGue5+I= +github.com/shogo82148/pointer v1.3.0/go.mod h1:agZ5JFpavFPXznbWonIvbG78NDfvDTFppe+7o53up5w= diff --git a/order.go b/order.go index 75e9051..6692409 100644 --- a/order.go +++ b/order.go @@ -8,6 +8,11 @@ import ( // OrderType represents the order type. type OrderType string +// String returns the string representation of the OrderType. +func (o OrderType) String() string { + return string(o) +} + const ( // OrderTypeBuy is the order type of buy. OrderTypeBuy OrderType = "buy" diff --git a/rate.go b/rate.go index 4924caf..c36f922 100644 --- a/rate.go +++ b/rate.go @@ -2,6 +2,8 @@ package coincheck import ( "context" + "errors" + "fmt" "net/http" ) @@ -41,3 +43,63 @@ func (c *Client) GetRate(ctx context.Context, input GetRateInput) (*GetRateRespo } return &output, nil } + +// GetExchangeOrdersRateInput represents the input for the GetExchangeOrdersRate function. +// Either price or amount must be specified as a parameter. +// When both Price and Amount were specified, a status code of 400 was returned in the response. +type GetExchangeOrdersRateInput struct { + // OrderType is the order type. Order type("sell" or "buy") + OrderType OrderType + // Pair is the pair of the currency. e.g. btc_jpy. + // Specify a currency pair to trade. btc_jpy, etc_jpy, lsk_jpy, mona_jpy, plt_jpy, fnct_jpy, dai_jpy, wbtc_jpy, bril_jpy are now available. + Pair Pair + // Price is the price of the order. e.g. 30000. + Price *float64 + // Amount is the amount of the order. e.g. 0.1. + Amount *float64 +} + +// GetExchangeOrdersRateResponse represents the output from GetExchangeOrdersRate. +type GetExchangeOrdersRateResponse struct { + // Success is a boolean value that indicates the success of the API call. + Success bool `json:"success"` + // Rate is the rate of the order. + Rate string `json:"rate"` + // Price is the price of the order. + Price string `json:"price"` + // Amount is the amount of the order. + Amount string `json:"amount"` +} + +// GetExchangeOrdersRate calculate the rate from the order of the exchange. +// API: GET /api/exchange/orders/rate +// Visibility: Public +func (c *Client) GetExchangeOrdersRate(ctx context.Context, input GetExchangeOrdersRateInput) (*GetExchangeOrdersRateResponse, error) { + queryParam := map[string]string{ + "order_type": input.OrderType.String(), + "pair": input.Pair.String(), + } + if input.Price == nil && input.Amount == nil { + return nil, withPrefixError(errors.New("either price or amount must be specified as a parameter")) + } + if input.Price != nil { + queryParam["price"] = fmt.Sprintf("%f", *input.Price) + } else { + queryParam["amount"] = fmt.Sprintf("%f", *input.Amount) + } + + req, err := c.createRequest(ctx, createRequestInput{ + method: http.MethodGet, + path: "/api/exchange/orders/rate", + queryParam: queryParam, + }) + if err != nil { + return nil, err + } + + var output GetExchangeOrdersRateResponse + if err := c.do(req, &output); err != nil { + return nil, err + } + return &output, nil +} diff --git a/rate_test.go b/rate_test.go index 64a98de..a2d2981 100644 --- a/rate_test.go +++ b/rate_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/shogo82148/pointer" ) func TestClient_GetStandardRate(t *testing.T) { @@ -100,3 +101,161 @@ func TestClient_GetStandardRate(t *testing.T) { } }) } + +func TestClient_GetExchangeOrdersRate(t *testing.T) { + t.Run("GetExchangeOrdersRate returns the exchange orders rate (if input valuer set amount)", 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/exchange/orders/rate" + if got := r.URL.Path; got != wantEndpoint { + t.Errorf("Endpoint: got %v, want %v", got, wantEndpoint) + } + + wantPair := PairBTCJPY + if got := r.URL.Query().Get("pair"); got != wantPair.String() { + t.Errorf("pair: got %v, want %v", got, wantPair) + } + wantOrderType := OrderTypeBuy + if got := r.URL.Query().Get("order_type"); got != wantOrderType.String() { + t.Errorf("order_type: got %v, want %v", got, wantOrderType) + } + wantAmount := "1.200000" + if got := r.URL.Query().Get("amount"); got != wantAmount { + t.Errorf("amount: got %v, want %v", got, wantAmount) + } + + result := GetExchangeOrdersRateResponse{ + Success: true, + Rate: "9118315.44305", + Price: "10941978.53166054", + Amount: "1.2", + } + if err := json.NewEncoder(w).Encode(result); err != nil { + t.Fatal(err) + } + })) + + // Create a new client + client, err := NewClient(WithBaseURL(testServer.URL)) + if err != nil { + t.Fatal(err) + } + + // Start testing + input := GetExchangeOrdersRateInput{ + OrderType: OrderTypeBuy, + Pair: PairBTCJPY, + Amount: pointer.Float64(1.2), + } + got, err := client.GetExchangeOrdersRate(context.Background(), input) + if err != nil { + t.Fatal(err) + } + want := &GetExchangeOrdersRateResponse{ + Success: true, + Rate: "9118315.44305", + Price: "10941978.53166054", + Amount: "1.2", + } + if diff := cmp.Diff(want, got); diff != "" { + printDiff(t, diff) + } + }) + + t.Run("GetExchangeOrdersRate returns the exchange orders rate (if input valuer set price)", 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/exchange/orders/rate" + if got := r.URL.Path; got != wantEndpoint { + t.Errorf("Endpoint: got %v, want %v", got, wantEndpoint) + } + + wantPair := PairBTCJPY + if got := r.URL.Query().Get("pair"); got != wantPair.String() { + t.Errorf("pair: got %v, want %v", got, wantPair) + } + wantOrderType := OrderTypeBuy + if got := r.URL.Query().Get("order_type"); got != wantOrderType.String() { + t.Errorf("order_type: got %v, want %v", got, wantOrderType) + } + wantPrice := "1200000.000000" + if got := r.URL.Query().Get("price"); got != wantPrice { + t.Errorf("price: got %v, want %v", got, wantPrice) + } + + result := GetExchangeOrdersRateResponse{ + Success: true, + Rate: "9118315.44305", + Price: "1200000", + Amount: "1.1", + } + if err := json.NewEncoder(w).Encode(result); err != nil { + t.Fatal(err) + } + })) + + // Create a new client + client, err := NewClient(WithBaseURL(testServer.URL)) + if err != nil { + t.Fatal(err) + } + + // Start testing + input := GetExchangeOrdersRateInput{ + OrderType: OrderTypeBuy, + Pair: PairBTCJPY, + Price: pointer.Float64(1200000), + } + got, err := client.GetExchangeOrdersRate(context.Background(), input) + if err != nil { + t.Fatal(err) + } + want := &GetExchangeOrdersRateResponse{ + Success: true, + Rate: "9118315.44305", + Price: "1200000", + Amount: "1.1", + } + if diff := cmp.Diff(want, got); diff != "" { + printDiff(t, diff) + } + }) + + t.Run("If the amount and pair are empty, GetExchangeOrdersRate returns an error", func(t *testing.T) { + client, err := NewClient() + if err != nil { + t.Fatal(err) + } + + if _, err = client.GetExchangeOrdersRate(context.Background(), GetExchangeOrdersRateInput{}); err == nil { + t.Fatal("expected an error, but got nil") + } + }) + + t.Run("GetExchangeOrdersRate returns an error", func(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + })) + + client, err := NewClient(WithBaseURL(testServer.URL)) + if err != nil { + t.Fatal(err) + } + + if _, err = client.GetExchangeOrdersRate(context.Background(), GetExchangeOrdersRateInput{ + OrderType: OrderTypeBuy, + Pair: PairBTCJPY, + Amount: pointer.Float64(1.2), + }); err == nil { + t.Fatal("expected an error, but got nil") + } + }) +}