Skip to content

Commit

Permalink
Merge pull request #5 from nao1215/nchika/add-calc-rate
Browse files Browse the repository at this point in the history
Add GetExchangeOrdersRate()
  • Loading branch information
nao1215 authored Aug 3, 2024
2 parents 3f1b808 + c7f047d commit 037d90b
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 1 deletion.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ✨

Expand Down Expand Up @@ -135,3 +139,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END -->

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.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
5 changes: 5 additions & 0 deletions order.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
62 changes: 62 additions & 0 deletions rate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package coincheck

import (
"context"
"errors"
"fmt"
"net/http"
)

Expand Down Expand Up @@ -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
}
159 changes: 159 additions & 0 deletions rate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

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

func TestClient_GetStandardRate(t *testing.T) {
Expand Down Expand Up @@ -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")
}
})
}

0 comments on commit 037d90b

Please sign in to comment.