diff --git a/README.md b/README.md index 203f637..b9739b3 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,35 @@ # go-newton-co -go api for http://newton.co/ bitcoin exchance +go api for http://newton.co/ cryptocurrency exchance -To create a client: +## Usage: -```go -n := New(, ) +For every API enpoint, there is an appropriate struct in the `query` package +that has all body and query parameters as part of the struct's fields. + +Once the appropriate query is selected, use `Do` function of the `newton` package with +the query provided. + +Example of calling Newton's `/balances` endpoint: - _, err := n.Balances("BTC") +```go +newton := New(, ) - if err != nil { - t.Error("test failed: " + err.Error()) - } +q := &query.Balances{Asset: "BTC"} +response, err := newton.Do(q) ``` -TODO: +For more examples, please reference `newton_test.go`. + +## Testing + +Newton's mock endpoint does not take authentication headers into account. To test the API with authentication enabled, +take a look at `auth_test.sh` where the following environment variables are set before running `go test`: +- TEST_AUTH - should be set to "true" +- CLIENT_ID - your real client id from Newton +- CLIENT_SECRET - your real client secret from Newton -- [x] API for private endpoints -- [x] API for public endpoints -- [ ] Add sandbox environment for unit tests +Running the script with the correct client id and client secret, will make +a request to Newton's production balances endpoint to ensure that authentication is functioning correctly. +On the other hand, running `go test` without these environment variables set will test every endpoint of the mock server. diff --git a/auth_test.sh b/auth_test.sh new file mode 100755 index 0000000..e3ddd7a --- /dev/null +++ b/auth_test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +TEST_AUTH=true CLIENT_ID=$1 CLIENT_SECRET=$2 go test diff --git a/go.mod b/go.mod index c8b25d9..afc5c81 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,12 @@ module github.com/dhiaayachi/go-newton-co -go 1.15 +go 1.17 + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/onsi/gomega v1.16.0 // indirect + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ad2e821 --- /dev/null +++ b/go.sum @@ -0,0 +1,87 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/newton.go b/newton.go index 02b6ef1..2d7ff1e 100644 --- a/newton.go +++ b/newton.go @@ -6,7 +6,6 @@ import ( "crypto/sha256" "encoding/base64" "encoding/json" - "errors" "fmt" "io/ioutil" "log" @@ -14,111 +13,25 @@ import ( "strconv" "strings" "time" + + "github.com/dhiaayachi/go-newton-co/query" ) const baseUrl = "https://api.newton.co/v1" -type ActionType string - -const ( - DEPOSIT ActionType = "DEPOSIT" - WITHDRAWAL ActionType = "WITHDRAWAL" - TRANSACT ActionType = "TRANSACT" -) - type Newton struct { - clientId string - clientSecret string -} - -type Args struct { - Key string - Value string -} - -type NewOrderReq struct { - OrderType string `json:"order_type"` - TimeInForce string `json:"time_in_force"` - Side string `json:"side"` - Symbol string `json:"symbol"` - Price float64 `json:"price"` - Quantity float64 `json:"quantity"` -} - -type GetTickSizesResp struct { - Ticks map[string]struct { - Tick float64 `json:"tick"` - } -} - -type GetMaxTradeAmountsResp struct { - TradeAmounts map[string]struct { - Buy float64 `json:"buy"` - Sell float64 `json:"sell"` - } -} - -type GetApplicableFeesResp struct { - Fees struct { - Maker float64 `json:"maker"` - Taker float64 `json:"taker"` - } -} - -type GetMinTradeAmountsResp GetMaxTradeAmountsResp - -type GetSymbolsResp struct { - Symbols []string -} - -type BalancesResp struct { - Balances map[string]float64 + ClientId string + ClientSecret string + BaseUrl string } -type OpenOrdersResp struct { - OpenOrders []struct { - OrderID string `json:"order_id"` - Symbol string `json:"symbol"` - Quantity int `json:"quantity"` - Price float64 `json:"price"` - DateCreated time.Time `json:"date_created"` - OrderType string `json:"order_type"` - TimeInForce string `json:"time_in_force"` - Side string `json:"side"` - QuantityLeft float64 `json:"quantity_left"` - ExpiryTime time.Time `json:"expiry_time"` - } -} - -type OrdersHistoryResp struct { - OrdersHistory []struct { - OrderID string `json:"order_id"` - Symbol string `json:"symbol"` - Quantity int `json:"quantity"` - Price float64 `json:"price"` - Status string `json:"status"` - DateCreated time.Time `json:"date_created"` - DateExecuted string `json:"date_executed"` - OrderType string `json:"order_type"` - TimeInForce string `json:"time_in_force"` - Side string `json:"side"` - ExpiryTime time.Time `json:"expiry_time,omitempty"` - } -} - -type ActionsResp struct { - Actions []struct { - Type string `json:"type"` - Asset string `json:"asset"` - Quantity float64 `json:"quantity"` - Status string `json:"status"` - DateCreated time.Time `json:"date_created"` - Price float64 `json:"price,omitempty"` - } +type Response struct { + StatusCode int + Body interface{} } func New(ClientId string, ClientSecret string) *Newton { - return &Newton{ClientId, ClientSecret} + return &Newton{ClientId, ClientSecret, baseUrl} } func (n *Newton) sign(req *http.Request) error { @@ -160,7 +73,7 @@ func (n *Newton) sign(req *http.Request) error { raw := strings.Join(toJoin, ":") - mac := hmac.New(sha256.New, []byte(n.clientSecret)) + mac := hmac.New(sha256.New, []byte(n.ClientSecret)) if _, err := mac.Write([]byte(raw)); err != nil { return fmt.Errorf("mac write: %w", err) } @@ -168,407 +81,85 @@ func (n *Newton) sign(req *http.Request) error { signedBase64 := base64.StdEncoding.EncodeToString(mac.Sum(nil)) req.Header.Add("NewtonDate", currentTime) - req.Header.Add("NewtonAPIAuth", n.clientId+":"+signedBase64) - - return nil -} - -// Public API -/////////////////////////////////////////////////////////////////////////////////////////////////// -func (n *Newton) doPublicQuery(path string, method string, args []Args, body string) (*http.Response, error) { - url := baseUrl + path - - req, _ := http.NewRequest(method, url, nil) - q := req.URL.Query() - for _, a := range args { - q.Add(a.Key, a.Value) - } - req.URL.RawQuery = q.Encode() - if method != http.MethodGet { - _, err := req.Body.Read([]byte(body)) - if err != nil { - return nil, err - } - req.Header.Add("content-type", "application/json") - } - - res, err := http.DefaultClient.Do(req) - - return res, err -} - -func (n *Newton) GetTickSizes() (*GetTickSizesResp, error) { - res, err := n.doPublicQuery("/order/tick-sizes", http.MethodGet, []Args{}, "") - if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - - body, _ := ioutil.ReadAll(res.Body) - - var resp GetTickSizesResp - err = json.Unmarshal(body, &resp.Ticks) - if err != nil { - return nil, err - } - - return &resp, nil -} - -func (n *Newton) GetMaximumTradeAmounts() (*GetMaxTradeAmountsResp, error) { - res, err := n.doPublicQuery("/order/maximums", http.MethodGet, []Args{}, "") - if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - - body, _ := ioutil.ReadAll(res.Body) - - var resp GetMaxTradeAmountsResp - err = json.Unmarshal(body, &resp.TradeAmounts) - if err != nil { - return nil, err - } - - return &resp, nil -} - -func (n *Newton) GetApplicableFees() (*GetApplicableFeesResp, error) { - res, err := n.doPublicQuery("/fees", http.MethodGet, []Args{}, "") - if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - - body, _ := ioutil.ReadAll(res.Body) - - var resp GetApplicableFeesResp - err = json.Unmarshal(body, &resp.Fees) - if err != nil { - return nil, err - } - - return &resp, nil -} - -func (n *Newton) GetSymbols(baseAsset, quoteAsset string) (*GetSymbolsResp, error) { - args := []Args{} - if baseAsset != "" { - args = append(args, Args{Key: "base_asset", Value: baseAsset}) - } - - if quoteAsset != "" { - args = append(args, Args{Key: "quote_asset", Value: quoteAsset}) - } - - res, err := n.doPublicQuery("/symbols", http.MethodGet, args, "") - if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - - body, _ := ioutil.ReadAll(res.Body) - - var resp GetSymbolsResp - err = json.Unmarshal(body, &resp.Symbols) - if err != nil { - return nil, err - } - - return &resp, nil -} - -func (n *Newton) HealthCheck() error { - res, err := n.doPublicQuery("/symbols", http.MethodGet, []Args{}, "") - if err != nil { - return err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } + req.Header.Add("NewtonAPIAuth", n.ClientId+":"+signedBase64) return nil } -func (n *Newton) GetMinimumTradeAmount() (*GetMinTradeAmountsResp, error) { - res, err := n.doPublicQuery("/order/minimums", http.MethodGet, []Args{}, "") +func (n *Newton) Do(query query.Query) (*Response, error) { + body, err := query.GetBody() if err != nil { return nil, err } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - body, _ := ioutil.ReadAll(res.Body) + req, _ := http.NewRequest( + query.GetMethod(), + n.BaseUrl+query.GetPath(), + bytes.NewBuffer(body)) - var resp GetMinTradeAmountsResp - err = json.Unmarshal(body, &resp.TradeAmounts) - if err != nil { - return nil, err - } - - return &resp, nil -} - -// Private API -/////////////////////////////////////////////////////////////////////////////////////////////////// -func (n *Newton) doPrivateQuery(path string, method string, args []Args, body string) (*http.Response, error) { - url := baseUrl + path - - req, _ := http.NewRequest(method, url, bytes.NewBuffer([]byte(body))) q := req.URL.Query() - for _, a := range args { + for _, a := range query.GetParameters() { q.Add(a.Key, a.Value) } + req.URL.RawQuery = q.Encode() - if method != http.MethodGet { + if query.GetMethod() != http.MethodGet { req.Header.Add("content-type", "application/json") } - err := n.sign(req) - if err != nil { - return nil, err - } - res, err := http.DefaultClient.Do(req) - - return res, err -} - -func (n *Newton) Balances(asset string) (*BalancesResp, error) { - - a := make([]Args, 1) - a[0].Key = "asset" - a[0].Value = asset - res, err := n.doPrivateQuery("/balances", http.MethodGet, a, "") - if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() + if !query.IsPublic() { + err := n.sign(req) if err != nil { - log.Printf("error:%s", err.Error()) + return nil, err } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) } - body, _ := ioutil.ReadAll(res.Body) - - var b BalancesResp - err = json.Unmarshal(body, &b.Balances) - if err != nil { - return nil, err - } - - return &b, nil -} - -func (n *Newton) Actions(actionType ActionType, limit int, offset int, startDate int64, endDate int64) (*ActionsResp, error) { - - a := make([]Args, 5) - - a[0].Key = "action_type" - a[0].Value = string(actionType) - - a[1].Key = "end_date" - a[1].Value = strconv.FormatInt(endDate, 10) - - a[2].Key = "limit" - a[2].Value = strconv.Itoa(limit) - - a[3].Key = "offset" - a[3].Value = strconv.Itoa(offset) - - a[4].Key = "start_date" - a[4].Value = strconv.FormatInt(startDate, 10) - - res, err := n.doPrivateQuery("/actions", http.MethodGet, a, "") + res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - body, _ := ioutil.ReadAll(res.Body) - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - var r ActionsResp - err = json.Unmarshal(body, &r.Actions) + parsedResponse, err := n.parseResponse(res, query.GetResponse()) if err != nil { return nil, err } - return &r, nil + return parsedResponse, err } -func (n *Newton) OrdersHistory(limit int, offset int, startDate int64, endDate int64, symbol string, timeInForce string) (*OrdersHistoryResp, error) { - - a := make([]Args, 10) - - a[1].Key = "end_date" - a[1].Value = strconv.FormatInt(endDate, 10) - - a[2].Key = "limit" - a[2].Value = strconv.Itoa(limit) - - a[3].Key = "offset" - a[3].Value = strconv.Itoa(offset) - - a[4].Key = "start_date" - a[4].Value = strconv.FormatInt(startDate, 10) - - a[5].Key = "symbol" - a[5].Value = symbol - - a[6].Key = "time_in_force" - a[6].Value = timeInForce - - res, err := n.doPrivateQuery("/order/history", http.MethodGet, a, "") - if err != nil { - return nil, err - } +func (n *Newton) parseResponse(res *http.Response, toParseTo interface{}) (*Response, error) { defer func() { err := res.Body.Close() if err != nil { log.Printf("error:%s", err.Error()) } }() - body, _ := ioutil.ReadAll(res.Body) - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - var r OrdersHistoryResp - err = json.Unmarshal(body, &r.OrdersHistory) - if err != nil { - return nil, err + parsedResponse := &Response{ + StatusCode: res.StatusCode, + Body: nil, } - return &r, nil -} - -func (n *Newton) OpenOrders(limit int, offset int, symbol string, timeInForce string) (*OpenOrdersResp, error) { - - a := make([]Args, 10) - - a[1].Key = "limit" - a[1].Value = strconv.Itoa(limit) - - a[2].Key = "offset" - a[2].Value = strconv.Itoa(offset) - - a[3].Key = "symbol" - a[3].Value = symbol - - a[4].Key = "time_in_force" - a[4].Value = timeInForce + body, err := ioutil.ReadAll(res.Body) - res, err := n.doPrivateQuery("/order/history", http.MethodGet, a, "") - if err != nil { - return nil, err + if toParseTo == nil { + return parsedResponse, nil } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("request failed :: %d", res.StatusCode)) - } - - body, _ := ioutil.ReadAll(res.Body) - var r OpenOrdersResp - err = json.Unmarshal(body, &r.OpenOrders) if err != nil { - return nil, err + return parsedResponse, err } - return &r, nil -} - -func (n *Newton) NewOrder(orderType string, timeInForce string, side string, symbol string, price float64, quantity float64) (*OpenOrdersResp, error) { - - order := NewOrderReq{orderType, timeInForce, side, symbol, price, quantity} - - b, err := json.Marshal(&order) - if err != nil { - return nil, err + if parsedResponse.StatusCode != http.StatusOK { + return parsedResponse, fmt.Errorf("request failed :: %d :: %s", res.StatusCode, body) } - res, err := n.doPrivateQuery("/order/new", http.MethodPost, nil, string(b)) + err = json.Unmarshal(body, toParseTo) if err != nil { - return nil, err - } - defer func() { - err := res.Body.Close() - if err != nil { - log.Printf("error:%s", err.Error()) - } - }() - if res.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(res.Body) - return nil, errors.New(fmt.Sprintf("request failed :: %d %s", res.StatusCode, body)) + return parsedResponse, err } - body, _ := ioutil.ReadAll(res.Body) - - var r OpenOrdersResp - err = json.Unmarshal(body, &r.OpenOrders) - if err != nil { - return nil, err - } + parsedResponse.Body = toParseTo - return &r, nil + return parsedResponse, nil } diff --git a/newton_test.go b/newton_test.go index f622849..ee25c03 100644 --- a/newton_test.go +++ b/newton_test.go @@ -1,125 +1,322 @@ -package newton +package newton_test import ( "os" "testing" "time" + + "github.com/dhiaayachi/go-newton-co" + "github.com/dhiaayachi/go-newton-co/query" + "github.com/onsi/gomega" +) + +const ( + mockClientId = "mock_id" + mock_client_secret = "mock_secret" + mockServerURL = "https://stoplight.io/mocks/newton/newton-api-docs/431375" + productionServerURL = "https://api.newton.co/v1" ) -func getSecrets() (string, string) { - return os.Getenv("CLIENT_ID"), os.Getenv("CLIENT_SECRET") +func TestNewNewton(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.New(mockClientId, mock_client_secret) + + g.Expect(sut.ClientId).Should(gomega.Equal(mockClientId)) + g.Expect(sut.ClientSecret).Should(gomega.Equal(mock_client_secret)) + g.Expect(sut.BaseUrl).Should(gomega.Equal(productionServerURL)) } // Public API /////////////////////////////////////////////////////////////////////////////////////////////////// func TestGetTickSizes(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) + g := gomega.NewGomegaWithT(t) - _, err := n.GetTickSizes() - - if err != nil { - t.Error("test failed: " + err.Error()) + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + _, err := sut.Do(&query.TickSizes{}) + + g.Expect(err).Should(gomega.BeNil()) } func TestGetMaximumTradeAmounts(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) + g := gomega.NewGomegaWithT(t) - _, err := n.GetMaximumTradeAmounts() - - if err != nil { - t.Error("test failed: " + err.Error()) + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + _, err := sut.Do(&query.MaximumTradeAmounts{}) + + g.Expect(err).Should(gomega.BeNil()) } func TestGetApplicableFees(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) - - _, err := n.GetApplicableFees() + g := gomega.NewGomegaWithT(t) - if err != nil { - t.Error("test failed: " + err.Error()) + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + _, err := sut.Do(&query.ApplicableFees{}) + + g.Expect(err).Should(gomega.BeNil()) } func TestSymbolsNoQuery(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) + g := gomega.NewGomegaWithT(t) - _, err := n.GetSymbols("", "") + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - if err != nil { - t.Error("test failed: " + err.Error()) + q := &query.Symbols{ + BaseAsset: query.NO_FILTER, + QuoteAsset: query.NO_FILTER, } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) } -func TestHealthCheck(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) +func TestSymbolsWithQuery(t *testing.T) { + g := gomega.NewGomegaWithT(t) - err := n.HealthCheck() + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - if err != nil { - t.Error("test failed: " + err.Error()) + q := &query.Symbols{ + BaseAsset: "BTC", + QuoteAsset: "ETH", } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) } -func TestGetMinTradeAmounts(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) +func TestHealthCheck(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - _, err := n.GetMinimumTradeAmount() + _, err := sut.Do(&query.HealthCheck{}) - if err != nil { - t.Error("test failed: " + err.Error()) + g.Expect(err).Should(gomega.BeNil()) +} + +func TestGetMinTradeAmounts(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + _, err := sut.Do(&query.MinimumTradeAmounts{}) + + g.Expect(err).Should(gomega.BeNil()) } // Private API /////////////////////////////////////////////////////////////////////////////////////////////////// -func TestBalance(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) +func TestBalances(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - _, err := n.Balances("BTC") + q := &query.Balances{Asset: "BTC"} + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) +} - if err != nil { - t.Error("test failed: " + err.Error()) +func TestBalancesNoQuery(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + q := &query.Balances{Asset: query.NO_FILTER} + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) } -func TestAction(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) +func TestActions(t *testing.T) { + g := gomega.NewGomegaWithT(t) - _, err := n.Actions(DEPOSIT, 1, 0, time.Date(2020, 01, 01, 00, 00, 00, 00, time.Local).Unix(), time.Date(2020, 01, 02, 00, 00, 00, 00, time.Local).Unix()) + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - if err != nil { - t.Error("test failed: " + err.Error()) + q := &query.Actions{ + ActionType: query.DEPOSIT, + Limit: 1, + Offset: 0, + StartDate: time.Date(2020, 01, 01, 00, 00, 00, 00, time.Local).Unix(), + EndDate: time.Date(2020, 01, 02, 00, 00, 00, 00, time.Local).Unix(), } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) +} + +func TestActionsNoQuery(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } + + req := &query.Actions{ + ActionType: query.ActionType(query.NO_FILTER), + Limit: int(query.ANY), + Offset: int(query.ANY), + StartDate: int64(query.ANY), + EndDate: int64(query.ANY), + } + + _, err := sut.Do(req) + + g.Expect(err).Should(gomega.BeNil()) } func TestOrderHistory(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) + g := gomega.NewGomegaWithT(t) - _, err := n.OrdersHistory(1, 0, time.Date(2020, 01, 01, 00, 00, 00, 00, time.Local).Unix(), time.Date(2020, 01, 01, 01, 00, 00, 00, time.Local).Unix(), "", "") + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } - if err != nil { - t.Error("test failed: " + err.Error()) + q := &query.OrderHistory{ + Limit: 1, + Offset: 0, + StartDate: time.Date(2020, 01, 01, 00, 00, 00, 00, time.Local).Unix(), + EndDate: time.Date(2020, 01, 01, 01, 00, 00, 00, time.Local).Unix(), + Symbol: "BTC_USDC", + TimeInForce: "IOC", } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) } func TestOpenOrders(t *testing.T) { - ClientId, ClientSecret := getSecrets() - n := New(ClientId, ClientSecret) + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } + + q := &query.OpenOrders{ + Limit: 1, + Offset: 0, + Symbol: "BTC_USDC", + TimeInForce: "IOC"} + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) +} - _, err := n.OpenOrders(1, 0, "", "") +func TestNewOrder(t *testing.T) { + g := gomega.NewGomegaWithT(t) - if err != nil { - t.Error("test failed: " + err.Error()) + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, } + + q := &query.NewOrder{ + Body: query.NewOrderBody{ + OrderType: "LIMIT", + TimeInForce: query.IOC, + Side: "BUY", + Symbol: "BTC_USDC", + Price: 1000, + Quantity: 0.0001, + }, + } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) +} + +func TestCancelOrder(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := newton.Newton{ + mockClientId, + mock_client_secret, + mockServerURL, + } + + q := &query.CancelOrder{ + Body: query.CancelOrderBody{ + OrderId: "test", + }, + } + + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) +} + +// Newton's mock server does not utilize authnetication headers. +// Therefore, to fully test the API, we must test any private production endpoint +// with real credentials. +func TestAuthentication(t *testing.T) { + if os.Getenv("TEST_AUTH") != "true" { + t.Skip("Skipping authentication test in production environment.") + } + g := gomega.NewGomegaWithT(t) + + productionClientId := os.Getenv("CLIENT_ID") + productionClientSecret := os.Getenv("CLIENT_SECRET") + + sut := newton.New(productionClientId, productionClientSecret) + + q := &query.Balances{Asset: "BTC"} + _, err := sut.Do(q) + + g.Expect(err).Should(gomega.BeNil()) } diff --git a/query/actions.go b/query/actions.go new file mode 100644 index 0000000..0eeb49e --- /dev/null +++ b/query/actions.go @@ -0,0 +1,72 @@ +package query + +import ( + "net/http" + "strconv" + "time" +) + +type Actions struct { + ActionType ActionType + Limit int + Offset int + StartDate int64 + EndDate int64 +} + +type ActionsResponse []struct { + Type string `json:"type"` + Asset string `json:"asset"` + Quantity float64 `json:"quantity"` + Status string `json:"status"` + DateCreated time.Time `json:"date_created"` + Price float64 `json:"price,omitempty"` +} + +const actionsPath = "/actions" + +func (a Actions) GetMethod() string { + return http.MethodGet +} + +func (a Actions) GetPath() string { + return actionsPath +} + +func (a Actions) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (a Actions) GetParameters() []Parameter { + params := make([]Parameter, 0) + + if a.ActionType != ActionType(NO_FILTER) { + params = append(params, Parameter{string(ActionTypeKey), string(a.ActionType)}) + } + + if a.Limit != ANY { + params = append(params, Parameter{string(Limit), strconv.Itoa(a.Limit)}) + } + + if a.Offset != ANY { + params = append(params, Parameter{string(Offset), strconv.Itoa(a.Offset)}) + } + + if a.StartDate != int64(ANY) { + params = append(params, Parameter{string(StartDate), strconv.FormatInt(a.StartDate, 10)}) + } + + if a.EndDate != int64(ANY) { + params = append(params, Parameter{string(EndDate), strconv.FormatInt(a.EndDate, 10)}) + } + + return params +} + +func (a Actions) GetResponse() interface{} { + return &ActionsResponse{} +} + +func (a Actions) IsPublic() bool { + return false +} diff --git a/query/actions_test.go b/query/actions_test.go new file mode 100644 index 0000000..a9b47ad --- /dev/null +++ b/query/actions_test.go @@ -0,0 +1,109 @@ +package query_test + +import ( + "net/http" + "reflect" + "strconv" + "testing" + "time" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +const ( + limit = 1 + offset = 2 + actionType query.ActionType = query.DEPOSIT +) + +var ( + startTime = time.Now().Unix() + endTime = startTime + 1 + validActions = query.Actions{ + actionType, + limit, + offset, + startTime, + endTime, + } +) + +func TestActionsGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validActions + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestActionsGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validActions + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestActionsGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validActions + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.ActionsPath)) +} + +func TestActionsGetParametersNoFilter(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Actions{ + query.ActionType(query.NO_FILTER), + int(query.ANY), + int(query.ANY), + int64(query.ANY), + int64(query.ANY), + } + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestActionsGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validActions + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(5)) + + g.Expect(parameters).Should(gomega.ContainElements( + gomega.BeEquivalentTo(query.Parameter{string(query.ActionTypeKey), string(actionType)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Limit), strconv.Itoa(limit)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Offset), strconv.Itoa(offset)}), + gomega.BeEquivalentTo(query.Parameter{string(query.StartDate), strconv.FormatInt(startTime, 10)}), + gomega.BeEquivalentTo(query.Parameter{string(query.EndDate), strconv.FormatInt(endTime, 10)}), + )) +} + +func TestActionsGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Actions{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.ActionsResponse{}))) +} + +func TestActionsIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validActions + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/applicable_fees.go b/query/applicable_fees.go new file mode 100644 index 0000000..8524104 --- /dev/null +++ b/query/applicable_fees.go @@ -0,0 +1,36 @@ +package query + +import "net/http" + +type ApplicableFees struct {} + +type ApplicableFeesResponse struct { + Maker float64 `json:"maker"` + Taker float64 `json:"taker"` +} + +const applicableFeesPath = "/fees" + +func (af ApplicableFees) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (af ApplicableFees) GetMethod() string { + return http.MethodGet +} + +func (af ApplicableFees) GetPath() string { + return applicableFeesPath +} + +func (af ApplicableFees) GetParameters() []Parameter { + return []Parameter{} +} + +func (af ApplicableFees) GetResponse() interface{} { + return &ApplicableFeesResponse{} +} + +func (af ApplicableFees) IsPublic() bool { + return true +} diff --git a/query/applicable_fees_test.go b/query/applicable_fees_test.go new file mode 100644 index 0000000..e6b07e5 --- /dev/null +++ b/query/applicable_fees_test.go @@ -0,0 +1,65 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +func TestApplicableFeesGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestApplicableFeesGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestApplicableFeesGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.ApplicableFeesPath)) +} + +func TestApplicableFeesGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestApplicableFeesGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.ApplicableFeesResponse{}))) +} + +func TestApplicableFeesIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +} diff --git a/query/balances.go b/query/balances.go new file mode 100644 index 0000000..95a58c9 --- /dev/null +++ b/query/balances.go @@ -0,0 +1,41 @@ +package query + +import "net/http" + +type Balances struct { + Asset string +} + +type BalancesResponse map[string]float64 + +const balancesPath = "/balances" + +func (b Balances) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (b Balances) GetMethod() string { + return http.MethodGet +} + +func (b Balances) GetPath() string { + return balancesPath +} + +func (b Balances) GetParameters() []Parameter { + params := make([]Parameter, 0) + + if b.Asset != NO_FILTER { + params = append(params, Parameter{string(Asset), b.Asset}) + } + + return params +} + +func (b Balances) GetResponse() interface{} { + return &Balances{} +} + +func (b Balances) IsPublic() bool { + return false +} diff --git a/query/balances_test.go b/query/balances_test.go new file mode 100644 index 0000000..5e8817e --- /dev/null +++ b/query/balances_test.go @@ -0,0 +1,86 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +const asset = "BTC" +var validBalances query.Balances = query.Balances{ + asset, +} + +func TestBalancesGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validBalances + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestBalancesGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validBalances + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestBalancesGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.ApplicableFees{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.ApplicableFeesPath)) +} + +func TestBalancesGetParametersNoFilter(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Balances{ + query.NO_FILTER, + } + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestBalancesGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validBalances + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(1)) + + g.Expect(parameters).Should(gomega.ContainElements( + gomega.BeEquivalentTo(query.Parameter{string(query.Asset), asset}), + )) +} + +func TestBalancesGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Balances{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.BalancesResponse{}))) +} + +func TestBalancesIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validBalances + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/cancel_order.go b/query/cancel_order.go new file mode 100644 index 0000000..067997b --- /dev/null +++ b/query/cancel_order.go @@ -0,0 +1,48 @@ +package query + +import ( + "encoding/json" + "net/http" +) + +type CancelOrderBody struct { + OrderId string `json:"order_id"` +} + +type CancelOrder struct { + Body CancelOrderBody +} + +type CancelOrderResponse struct { + OrderID string `json:"order_id"` +} + +const cancelOrderPath = "/order/cancel" + +func (co CancelOrder) GetBody() ([]byte, error) { + body, err := json.Marshal(co.Body) + if err != nil { + return []byte(EMPTY_BODY), err + } + return body, nil +} + +func (co CancelOrder) GetMethod() string { + return http.MethodPost +} + +func (co CancelOrder) GetPath() string { + return cancelOrderPath +} + +func (co CancelOrder) GetParameters() []Parameter { + return []Parameter{} +} + +func (co CancelOrder) GetResponse() interface{} { + return &CancelOrderResponse{} +} + +func (co CancelOrder) IsPublic() bool { + return false +} diff --git a/query/cancel_order_test.go b/query/cancel_order_test.go new file mode 100644 index 0000000..8e4f414 --- /dev/null +++ b/query/cancel_order_test.go @@ -0,0 +1,80 @@ +package query_test + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +var ( + cancelOrderBody query.CancelOrderBody = query.CancelOrderBody{ + OrderId: "8c1be04a-813d-4abd-8b15-9367686afaed", + } + validCancelOrder query.CancelOrder = query.CancelOrder{ + Body: cancelOrderBody, + } +) + +func TestCancelOrderGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validCancelOrder + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + + var actualBodyParsed query.CancelOrderBody + err = json.Unmarshal([]byte(actualBody), &actualBodyParsed) + g.Expect(err).Should(gomega.BeNil()) + + g.Expect(actualBodyParsed).Should(gomega.BeEquivalentTo(cancelOrderBody)) +} + +func TestCancelOrderGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.CancelOrder{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodPost)) +} + +func TestCancelOrderGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.CancelOrder{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.CancelOrderPath)) +} + +func TestCancelOrderGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validCancelOrder + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestCancelOrderGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.CancelOrder{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.CancelOrderResponse{}))) +} + +func TestCancelOrderIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validCancelOrder + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/common.go b/query/common.go new file mode 100644 index 0000000..48196c6 --- /dev/null +++ b/query/common.go @@ -0,0 +1,53 @@ +package query + +type ActionType string + +const ( + DEPOSIT ActionType = "DEPOSIT" + WITHDRAWAL ActionType = "WITHDRAWAL" + TRANSACT ActionType = "TRANSACT" +) + +type QueryParameterKey string + +const ( + Asset QueryParameterKey = "asset" + ActionTypeKey QueryParameterKey = "action_type" + Limit QueryParameterKey = "limit" + Offset QueryParameterKey = "offset" + StartDate QueryParameterKey = "start_date" + EndDate QueryParameterKey = "end_date" + Symbol QueryParameterKey = "symbol" + TimeInForce QueryParameterKey = "time_in_force" + BaseAsset = "base_asset" + QuoteAsset = "quote_asset" +) + +type TimeInForceAllowedValue string + +const ( + NO_FILTER_VALUE TimeInForceAllowedValue = "" + IOC TimeInForceAllowedValue = "IOC" + GTC TimeInForceAllowedValue = "GTC" + GTD TimeInForceAllowedValue = "GTD" +) + +const ( + ANY int = -1 + NO_FILTER string = "" + EMPTY_BODY = "" +) + +type Parameter struct { + Key string + Value string +} + +type Query interface { + GetBody() ([]byte, error) + GetMethod() string + GetPath() string + GetParameters() []Parameter + GetResponse() interface{} + IsPublic() bool +} diff --git a/query/export_test.go b/query/export_test.go new file mode 100644 index 0000000..ee5abcc --- /dev/null +++ b/query/export_test.go @@ -0,0 +1,16 @@ +package query + +const ( + ActionsPath = actionsPath + ApplicableFeesPath = applicableFeesPath + BalancesPath = balancesPath + CancelOrderPath = cancelOrderPath + HealthCheckPath = healthCheckPath + MaximumTradeAmountsPath = maximumTradeAmountsPath + MinimumTradeAmountsPath = minimumTradeAmountsPath + NewOrderPath = newOrderPath + OpenOrdersPath = openOrdersPath + OrderHistoryPath = orderHistoryPath + SymbolsPath = symbolsPath + TickSizesPath = tickSizesPath +) diff --git a/query/health_check.go b/query/health_check.go new file mode 100644 index 0000000..35a4d9f --- /dev/null +++ b/query/health_check.go @@ -0,0 +1,31 @@ +package query + +import "net/http" + +type HealthCheck struct {} + +const healthCheckPath = "/health-check" + +func (hc HealthCheck) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (hc HealthCheck) GetMethod() string { + return http.MethodGet +} + +func (hc HealthCheck) GetPath() string { + return healthCheckPath +} + +func (hc HealthCheck) GetParameters() []Parameter { + return []Parameter{} +} + +func (hc HealthCheck) GetResponse() interface{} { + return nil +} + +func (hc HealthCheck) IsPublic() bool { + return true +} diff --git a/query/health_check_test.go b/query/health_check_test.go new file mode 100644 index 0000000..616aa94 --- /dev/null +++ b/query/health_check_test.go @@ -0,0 +1,65 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +func TestHealthCheckGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestHealthCheckGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestHealthCheckGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.HealthCheckPath)) +} + +func TestHealthCheckGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestHealthCheckGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.BeNil()) +} + +func TestHealthCheckIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.HealthCheck{} + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +} diff --git a/query/maximum_trade_amounts.go b/query/maximum_trade_amounts.go new file mode 100644 index 0000000..f2d39dd --- /dev/null +++ b/query/maximum_trade_amounts.go @@ -0,0 +1,36 @@ +package query + +import "net/http" + +type MaximumTradeAmounts struct {} + +type MaximumTradeAmountsResponse map[string]struct { + Buy float64 `json:"buy"` + Sell float64 `json:"sell"` +} + +const maximumTradeAmountsPath = "/order/maximums" + +func (mta MaximumTradeAmounts) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (mta MaximumTradeAmounts) GetMethod() string { + return http.MethodGet +} + +func (mta MaximumTradeAmounts) GetPath() string { + return maximumTradeAmountsPath +} + +func (mta MaximumTradeAmounts) GetParameters() []Parameter { + return []Parameter{} +} + +func (mta MaximumTradeAmounts) GetResponse() interface{} { + return &MaximumTradeAmountsResponse{} +} + +func (mta MaximumTradeAmounts) IsPublic() bool { + return true +} diff --git a/query/maximum_trade_amounts_test.go b/query/maximum_trade_amounts_test.go new file mode 100644 index 0000000..4266662 --- /dev/null +++ b/query/maximum_trade_amounts_test.go @@ -0,0 +1,65 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +func TestMaximumTradeAmountsGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestMaximumTradeAmountsGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestMaximumTradeAmountsGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.MaximumTradeAmountsPath)) +} + +func TestMaximumTradeAmountsGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestMaximumTradeAmountsGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.MaximumTradeAmountsResponse{}))) +} + +func TestMaximumTradeAmountsIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MaximumTradeAmounts{} + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +} diff --git a/query/minimum_trade_amounts.go b/query/minimum_trade_amounts.go new file mode 100644 index 0000000..2e122a1 --- /dev/null +++ b/query/minimum_trade_amounts.go @@ -0,0 +1,36 @@ +package query + +import "net/http" + +type MinimumTradeAmounts struct {} + +type MinimumTradeAmountsResponse map[string]struct { + Buy float64 `json:"buy"` + Sell float64 `json:"sell"` +} + +const minimumTradeAmountsPath = "/order/minimums" + +func (mta MinimumTradeAmounts) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (mta MinimumTradeAmounts) GetMethod() string { + return http.MethodGet +} + +func (mta MinimumTradeAmounts) GetPath() string { + return minimumTradeAmountsPath +} + +func (mta MinimumTradeAmounts) GetParameters() []Parameter { + return []Parameter{} +} + +func (mta MinimumTradeAmounts) GetResponse() interface{} { + return &MinimumTradeAmountsResponse{} +} + +func (mta MinimumTradeAmounts) IsPublic() bool { + return true +} diff --git a/query/minimum_trade_amounts_test.go b/query/minimum_trade_amounts_test.go new file mode 100644 index 0000000..6446029 --- /dev/null +++ b/query/minimum_trade_amounts_test.go @@ -0,0 +1,65 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +func TestMinimumTradeAmountsGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestMinimumTradeAmountsGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestMinimumTradeAmountsGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.MinimumTradeAmountsPath)) +} + +func TestMinimumTradeAmountGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestMinimumTradeAmountsGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.MinimumTradeAmountsResponse{}))) +} + +func TestMinimumTradeAmountIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.MinimumTradeAmounts{} + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +} diff --git a/query/new_order.go b/query/new_order.go new file mode 100644 index 0000000..9c66bba --- /dev/null +++ b/query/new_order.go @@ -0,0 +1,61 @@ +package query + +import ( + "encoding/json" + "net/http" + "time" +) + +type NewOrderBody struct { + OrderType string `json:"order_type"` + TimeInForce TimeInForceAllowedValue `json:"time_in_force"` + Side string `json:"side"` + Symbol string `json:"symbol"` + Price float64 `json:"price"` + Quantity float64 `json:"quantity"` +} + +type NewOrder struct { + Body NewOrderBody +} + +type NewOrderResponse struct { + OrderId string `json:"order_id"` + Symbol string `json:"symbol"` + Quantity float64 `json:"quantity"` + Price float64 `json:"price"` + OrderType string `json:"order_type"` + TimeInForce string `json:"time_in_force"` + Side string `json:"side"` + DateCreated time.Time `json:"date_created"` +} + +const newOrderPath = "/order/new" + +func (no NewOrder) GetBody() ([]byte, error) { + body, err := json.Marshal(no.Body) + if err != nil { + return []byte(EMPTY_BODY), err + } + return body, nil +} + +func (no NewOrder) GetMethod() string { + return http.MethodPost +} + +func (no NewOrder) GetPath() string { + return newOrderPath +} + +func (no NewOrder) GetParameters() []Parameter { + return []Parameter{} +} + +func (no NewOrder) GetResponse() interface{} { + return &NewOrderResponse{} +} + +func (no NewOrder) IsPublic() bool { + return false +} diff --git a/query/new_order_test.go b/query/new_order_test.go new file mode 100644 index 0000000..0981761 --- /dev/null +++ b/query/new_order_test.go @@ -0,0 +1,85 @@ +package query_test + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +var ( + newOrderBody query.NewOrderBody = query.NewOrderBody{ + OrderType: "LIMIT", + TimeInForce: query.IOC, + Side: "BUY", + Symbol: "BTC_USDC", + Price: 10.0, + Quantity: 2.5, + } + validNewOrder query.NewOrder = query.NewOrder{ + Body: newOrderBody, + } +) + +func TestNewOrderGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validNewOrder + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + + var actualBodyParsed query.NewOrderBody + err = json.Unmarshal([]byte(actualBody), &actualBodyParsed) + g.Expect(err).Should(gomega.BeNil()) + + g.Expect(actualBodyParsed).Should(gomega.BeEquivalentTo(newOrderBody)) +} + +func TestNewOrderGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.NewOrder{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodPost)) +} + +func TestNewOrderGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.NewOrder{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.NewOrderPath)) +} + +func TestNewOrderGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validNewOrder + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestNewOrderGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.NewOrder{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.NewOrderResponse{}))) +} + +func TestNewOrderIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validNewOrder + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/open_orders.go b/query/open_orders.go new file mode 100644 index 0000000..539fb54 --- /dev/null +++ b/query/open_orders.go @@ -0,0 +1,71 @@ +package query + +import ( + "net/http" + "strconv" + "time" +) + +type OpenOrders struct { + Limit int + Offset int + Symbol string + TimeInForce TimeInForceAllowedValue +} + +type OpenOrdersResponse []struct { + OrderID string `json:"order_id"` + Symbol string `json:"symbol"` + Quantity int `json:"quantity"` + Price float64 `json:"price"` + DateCreated time.Time `json:"date_created"` + OrderType string `json:"order_type"` + TimeInForce string `json:"time_in_force"` + Side string `json:"side"` + QuantityLeft float64 `json:"quantity_left"` + ExpiryTime time.Time `json:"expiry_time"` +} + +const openOrdersPath = "/order/open" + +func (oo OpenOrders) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (oo OpenOrders) GetMethod() string { + return http.MethodGet +} + +func (oo OpenOrders) GetPath() string { + return openOrdersPath +} + +func (oo OpenOrders) GetParameters() []Parameter { + params := make([]Parameter, 0) + + if oo.Limit != ANY { + params = append(params, Parameter{string(Limit), strconv.Itoa(oo.Limit)}) + } + + if oo.Offset != ANY { + params = append(params, Parameter{string(Offset), strconv.Itoa(oo.Offset)}) + } + + if oo.Symbol != NO_FILTER { + params = append(params, Parameter{string(Symbol), oo.Symbol}) + } + + if string(oo.TimeInForce) != NO_FILTER { + params = append(params, Parameter{string(TimeInForce), string(oo.TimeInForce)}) + } + + return params +} + +func (oo OpenOrders) GetResponse() interface{} { + return &OpenOrdersResponse{} +} + +func (oo OpenOrders) IsPublic() bool { + return false +} diff --git a/query/open_orders_test.go b/query/open_orders_test.go new file mode 100644 index 0000000..e54f117 --- /dev/null +++ b/query/open_orders_test.go @@ -0,0 +1,99 @@ +package query_test + +import ( + "net/http" + "reflect" + "strconv" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) +const( + symbol = "BTC_USDC" + timeInForce = query.IOC +) + +var validOpenOrders query.OpenOrders = query.OpenOrders{ + limit, + offset, + symbol, + timeInForce, +} + +func TestOpenOrdersGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOpenOrders + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestOpenOrdersGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOpenOrders + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestOpenOrdersGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOpenOrders + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.OpenOrdersPath)) +} + +func TestOpenOrdersGetParametersNoFilter(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.OpenOrders{ + query.ANY, + query.ANY, + query.NO_FILTER, + query.NO_FILTER_VALUE, + } + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestOpenOrdersGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOpenOrders + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(4)) + + g.Expect(parameters).Should(gomega.ContainElements( + gomega.BeEquivalentTo(query.Parameter{string(query.Limit), strconv.Itoa(limit)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Offset), strconv.Itoa(offset)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Symbol), symbol}), + gomega.BeEquivalentTo(query.Parameter{string(query.TimeInForce), string(timeInForce)}), + )) +} + +func TestOpenOrdersGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.OpenOrders{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.OpenOrdersResponse{}))) +} + +func TestOpenOrdersIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOpenOrders + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/order_history.go b/query/order_history.go new file mode 100644 index 0000000..687a80c --- /dev/null +++ b/query/order_history.go @@ -0,0 +1,82 @@ +package query + +import ( + "net/http" + "strconv" + "time" +) + +type OrderHistory struct { + Limit int + Offset int + StartDate int64 + EndDate int64 + Symbol string + TimeInForce TimeInForceAllowedValue +} + +type OrderHistoryResponse []struct { + OrderID string `json:"order_id"` + Symbol string `json:"symbol"` + Quantity int `json:"quantity"` + Price float64 `json:"price"` + Status string `json:"status"` + DateCreated time.Time `json:"date_created"` + DateExecuted string `json:"date_executed"` + OrderType string `json:"order_type"` + TimeInForce string `json:"time_in_force"` + Side string `json:"side"` + ExpiryTime time.Time `json:"expiry_time,omitempty"` +} + +const orderHistoryPath = "/order/history" + +func (oh OrderHistory) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (oh OrderHistory) GetMethod() string { + return http.MethodGet +} + +func (oh OrderHistory) GetPath() string { + return orderHistoryPath +} + +func (oh OrderHistory) GetParameters() []Parameter { + params := make([]Parameter, 0) + + if oh.Limit != ANY { + params = append(params, Parameter{string(Limit), strconv.Itoa(oh.Limit)}) + } + + if oh.Offset != ANY { + params = append(params, Parameter{string(Offset), strconv.Itoa(oh.Offset)}) + } + + if oh.StartDate != int64(ANY) { + params = append(params, Parameter{string(StartDate), strconv.Itoa(int(oh.StartDate))}) + } + + if oh.EndDate != int64(ANY) { + params = append(params, Parameter{string(EndDate), strconv.Itoa(int(oh.EndDate))}) + } + + if oh.Symbol != NO_FILTER { + params = append(params, Parameter{string(Symbol), oh.Symbol}) + } + + if string(oh.TimeInForce) != NO_FILTER { + params = append(params, Parameter{string(TimeInForce), string(oh.TimeInForce)}) + } + + return params +} + +func (oh OrderHistory) GetResponse() interface{} { + return &OrderHistoryResponse{} +} + +func (oh OrderHistory) IsPublic() bool { + return false +} diff --git a/query/order_history_test.go b/query/order_history_test.go new file mode 100644 index 0000000..217099e --- /dev/null +++ b/query/order_history_test.go @@ -0,0 +1,106 @@ +package query_test + +import ( + "net/http" + "reflect" + "strconv" + "testing" + "time" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +var ( + startDate = time.Now().Unix() + endDate = startDate + 1 + validOrderHistory = query.OrderHistory{ + limit, + offset, + startDate, + endDate, + symbol, + timeInForce, + } +) + +func TestOrderHistoryGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOrderHistory + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestOrderHistoryGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOrderHistory + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestOrderHistoryGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOrderHistory + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.OrderHistoryPath)) +} + +func TestOrdersHistoryGetParametersNoFilter(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.OrderHistory{ + query.ANY, + query.ANY, + int64(query.ANY), + int64(query.ANY), + query.NO_FILTER, + query.NO_FILTER_VALUE, + } + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestOrdersHistoryGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOrderHistory + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(6)) + + g.Expect(parameters).Should(gomega.ContainElements( + gomega.BeEquivalentTo(query.Parameter{string(query.Limit), strconv.Itoa(limit)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Offset), strconv.Itoa(offset)}), + gomega.BeEquivalentTo(query.Parameter{string(query.StartDate), strconv.FormatInt(startDate, 10)}), + gomega.BeEquivalentTo(query.Parameter{string(query.EndDate), strconv.FormatInt(endDate, 10)}), + gomega.BeEquivalentTo(query.Parameter{string(query.Symbol), symbol}), + gomega.BeEquivalentTo(query.Parameter{string(query.TimeInForce), string(timeInForce)}), + )) +} + +func TestOrderHistoryGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.OrderHistory{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.OrderHistoryResponse{}))) +} + +func TestOrderHistoryIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validOrderHistory + + g.Expect(sut.IsPublic()).Should(gomega.BeFalse()) +} diff --git a/query/symbols.go b/query/symbols.go new file mode 100644 index 0000000..dc765f2 --- /dev/null +++ b/query/symbols.go @@ -0,0 +1,45 @@ +package query + +import "net/http" + +type Symbols struct { + BaseAsset, QuoteAsset string +} + +type SymbolsResponse []string + +const symbolsPath = "/symbols" + +func (s Symbols) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (s Symbols) GetMethod() string { + return http.MethodGet +} + +func (s Symbols) GetPath() string { + return symbolsPath +} + +func (s Symbols) GetParameters() []Parameter { + params := make([]Parameter, 0) + + if s.BaseAsset != NO_FILTER { + params = append(params, Parameter{string(BaseAsset), s.BaseAsset}) + } + + if s.QuoteAsset != NO_FILTER { + params = append(params, Parameter{string(QuoteAsset), s.QuoteAsset}) + } + + return params +} + +func (s Symbols) GetResponse() interface{} { + return &SymbolsResponse{} +} + +func (s Symbols) IsPublic() bool { + return true +} diff --git a/query/symbols_test.go b/query/symbols_test.go new file mode 100644 index 0000000..c812895 --- /dev/null +++ b/query/symbols_test.go @@ -0,0 +1,93 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +const ( + baseAsset = "BTC" + quoteAsset = "ETH" +) + +var validSymbols = query.Symbols{ + baseAsset, + quoteAsset, +} + +func TestSymbolsGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validSymbols + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestSymbolsGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validSymbols + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestSymbolsGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validSymbols + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.SymbolsPath)) +} + +func TestSymbolsGetParametersNoFilter(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Symbols{ + query.NO_FILTER, + query.NO_FILTER, + } + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestSymbolsGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validSymbols + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(2)) + + g.Expect(parameters).Should(gomega.ContainElements( + gomega.BeEquivalentTo(query.Parameter{string(query.BaseAsset), baseAsset}), + gomega.BeEquivalentTo(query.Parameter{string(query.QuoteAsset), quoteAsset}), + )) +} + +func TestSymbolsGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.Symbols{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.SymbolsResponse{}))) +} + +func TestSymbolsIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &validSymbols + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +} diff --git a/query/tick_sizes.go b/query/tick_sizes.go new file mode 100644 index 0000000..caa4500 --- /dev/null +++ b/query/tick_sizes.go @@ -0,0 +1,35 @@ +package query + +import "net/http" + +type TickSizes struct {} + +type TickSizesResponse map[string]struct { + Tick float64 `json:"tick"` +} + +const tickSizesPath = "/order/tick-sizes" + +func (ts TickSizes) GetBody() ([]byte, error) { + return []byte(EMPTY_BODY), nil +} + +func (ts TickSizes) GetMethod() string { + return http.MethodGet +} + +func (ts TickSizes) GetPath() string { + return tickSizesPath +} + +func (ts TickSizes) GetParameters() []Parameter { + return []Parameter{} +} + +func (ts TickSizes) GetResponse() interface{} { + return &TickSizesResponse{} +} + +func (ts TickSizes) IsPublic() bool { + return true +} diff --git a/query/tick_sizes_test.go b/query/tick_sizes_test.go new file mode 100644 index 0000000..aa7f635 --- /dev/null +++ b/query/tick_sizes_test.go @@ -0,0 +1,65 @@ +package query_test + +import ( + "net/http" + "reflect" + "testing" + + "github.com/dhiaayachi/go-newton-co/query" + + "github.com/onsi/gomega" +) + +func TestTickSizesGetBody(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + actualBody, err := sut.GetBody() + g.Expect(err).Should(gomega.BeNil()) + g.Expect(actualBody).Should(gomega.BeEquivalentTo(query.EMPTY_BODY)) +} + +func TestTickSizesGetMethod(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + g.Expect(sut.GetMethod()).Should(gomega.Equal(http.MethodGet)) +} + +func TestTickSizesGetPath(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + g.Expect(sut.GetPath()).Should(gomega.Equal(query.TickSizesPath)) +} + +func TestTickSizesGetParameters(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + parameters := sut.GetParameters() + + g.Expect(len(parameters)).Should(gomega.Equal(0)) +} + +func TestTickSizesGetResponse(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + response := sut.GetResponse() + + g.Expect(reflect.TypeOf(response)).Should(gomega.Equal(reflect.TypeOf(&query.TickSizesResponse{}))) +} + +func TestTickSizesIsPublic(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + sut := &query.TickSizes{} + + g.Expect(sut.IsPublic()).Should(gomega.BeTrue()) +}