Skip to content

Commit

Permalink
Merge pull request #1 from giansalex/feature/crypto-api-v2
Browse files Browse the repository at this point in the history
crypto.com api v2
  • Loading branch information
giansalex authored Jun 15, 2020
2 parents 2c5eb22 + 0980cdc commit 872bc2c
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 83 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Build executable `go build -ldflags "-s -w" -o crypto`.
## Run
> You can use [docker image](https://hub.docker.com/r/giansalex/crypto-com-stoploss).
First create [API Keys](https://crypto.com/exchange-doc#generate-key).
First create [API Keys](https://crypto.com/exchange/personal/api-management).

Simple command to run bot stoploss -
Require environment variables: `CRYPTO_APIKEY`, `CRYPTO_SECRET`.
Expand Down
118 changes: 87 additions & 31 deletions crypto/api.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package crypto

import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
Expand All @@ -24,15 +27,15 @@ type API struct {
// NewAPI create new API
func NewAPI(apiKey string, apiSecret string) *API {

return &API{apiKey, apiSecret, http.DefaultClient, "https://api.crypto.com/v1/"}
return &API{apiKey, apiSecret, http.DefaultClient, "https://api.crypto.com/v2/"}
}

// GetPrice get current ticket price
func (api *API) GetPrice(ticket string) (*Price, error) {
params := url.Values{}
params.Add("symbol", ticket)
params.Add("instrument_name", ticket)

resp, err := api.client.Get(api.BasePath + "ticker?" + params.Encode())
resp, err := api.client.Get(api.BasePath + "public/get-ticker?" + params.Encode())

if err != nil {
return nil, err
Expand All @@ -44,20 +47,29 @@ func (api *API) GetPrice(ticket string) (*Price, error) {
json.NewDecoder(resp.Body).Decode(&response)

if response.Code != 0 {
return nil, errors.New(response.Msg)
return nil, errors.New(response.Message)
}

return &response.Data, nil
return &response.Result.Data, nil
}

// GetBalance account balance
func (api *API) GetBalance() ([]Balance, error) {
params := url.Values{}
params.Add("api_key", api.apiKey)
params.Add("time", fmt.Sprintf("%d", api.unixTime()))
params.Add("sign", api.createSign(params))
func (api *API) GetBalance(coin string) ([]Balance, error) {
method := "private/get-account-summary"

request := make(map[string]interface{})
request["id"] = api.createID()
request["method"] = method
request["params"] = map[string]interface{}{
"currency": coin,
}
request["api_key"] = api.apiKey
request["nonce"] = api.unixTime()

api.sign(request)

resp, err := api.client.PostForm(api.BasePath+"account", params)
payload, _ := json.Marshal(request)
resp, err := api.client.Post(api.BasePath+method, "application/json", bytes.NewBuffer(payload))

if err != nil {
return nil, err
Expand All @@ -68,49 +80,67 @@ func (api *API) GetBalance() ([]Balance, error) {
var response balanceResponse
json.NewDecoder(resp.Body).Decode(&response)

if response.Code != "0" {
return nil, errors.New(response.Msg)
if response.Code != 0 {
return nil, errors.New(response.Message)
}

return response.Data.CoinList, nil
return response.Result.Accounts, nil
}

// CreateOrder create order
func (api *API) CreateOrder(order Order) (int, error) {
params := url.Values{}
params.Add("api_key", api.apiKey)
func (api *API) CreateOrder(order Order) (string, error) {
method := "private/create-order"

params := map[string]interface{}{
"instrument_name": order.Symbol,
"side": order.Side,
"type": order.Type,
}

if order.Type == "LIMIT" {
params["price"] = order.Price
}

if order.Type == "1" {
params.Add("price", order.Price)
if order.Type == "MARKET" && order.Side == "BUY" {
params["notional"] = order.Quantity
} else {
params["quantity"] = order.Quantity
}

params.Add("side", order.Side)
params.Add("symbol", order.Symbol)
params.Add("time", fmt.Sprintf("%d", api.unixTime()))
params.Add("type", order.Type)
params.Add("volume", order.Volume)
params.Add("sign", api.createSign(params))
request := make(map[string]interface{})
request["id"] = api.createID()
request["method"] = method
request["params"] = params
request["api_key"] = api.apiKey
request["nonce"] = api.unixTime()

resp, err := api.client.PostForm(api.BasePath+"order", params)
api.sign(request)

payload, _ := json.Marshal(request)
resp, err := api.client.Post(api.BasePath+method, "application/json", bytes.NewBuffer(payload))

if err != nil {
return 0, err
return "", err
}

defer resp.Body.Close()

var response orderResponse
json.NewDecoder(resp.Body).Decode(&response)

if response.Code != "0" {
return 0, errors.New(response.Msg)
if response.Code != 0 {
return "", errors.New(response.Message)
}

return response.Data.OrderID, nil
return response.Result.OrderID, nil
}

func (api *API) createID() int64 {
return time.Now().UTC().Unix()
}

func (api *API) unixTime() int64 {
return time.Now().UTC().Unix() * 1000
return time.Now().UTC().UnixNano() / 1e6
}

func (api *API) createSign(data url.Values) string {
Expand All @@ -123,3 +153,29 @@ func (api *API) createSign(data url.Values) string {

return hex.EncodeToString(hash[:])
}

func (api *API) sign(request map[string]interface{}) {
params := request["params"].(map[string]interface{})
paramString := ""
for _, keySort := range api.getSortKeys(params) {
paramString += keySort + fmt.Sprintf("%v", params[keySort])
}
sigPayload := fmt.Sprintf("%v%v%s%s%v", request["method"], request["id"], api.apiKey, paramString, request["nonce"])

key := []byte(api.apiSecret)
mac := hmac.New(sha256.New, key)
mac.Write([]byte(sigPayload))

request["sig"] = hex.EncodeToString(mac.Sum(nil))
}

func (api *API) getSortKeys(params map[string]interface{}) []string {
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}

sort.Strings(keys)

return keys
}
20 changes: 11 additions & 9 deletions crypto/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package crypto

// Balance account balance
type Balance struct {
Normal string `json:"normal"`
Locked string `json:"locked"`
Coin string `json:"coin"`
Balance float64 `json:"balance"`
Available float64 `json:"available"`
Order float64 `json:"order"`
Stake float64 `json:"stake"`
Currency string `json:"currency"`
}

type balanceResponse struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
TotalAsset string `json:"total_asset"`
CoinList []Balance `json:"coin_list"`
} `json:"data"`
Code int `json:"code"`
Method string `json:"method"`
Message string `json:"message"`
Result struct {
Accounts []Balance `json:"accounts"`
} `json:"result"`
}
21 changes: 11 additions & 10 deletions crypto/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package crypto

// Order order parameters
type Order struct {
Price string `json:"price"`
Side string `json:"side"`
Symbol string `json:"symbol"`
Type string `json:"type"`
Volume string `json:"volume"`
Price float64 `json:"price"`
Side string `json:"side"`
Symbol string `json:"symbol"`
Type string `json:"type"`
Quantity float64 `json:"quantity"`
}

type orderResponse struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
OrderID int `json:"order_id"`
} `json:"data"`
Code int `json:"code"`
Method string `json:"method"`
Message string `json:"message"`
Result struct {
OrderID string `json:"order_id"`
} `json:"result"`
}
25 changes: 14 additions & 11 deletions crypto/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ package crypto

// Price price of crypto.com ticket
type Price struct {
High int `json:"high"`
Vol string `json:"vol"`
Last string `json:"last"`
Low string `json:"low"`
Buy string `json:"buy"`
Sell string `json:"sell"`
Rose string `json:"rose"`
Time int64 `json:"time"`
High float64 `json:"h"`
Vol float64 `json:"v"`
Last float64 `json:"a"`
Low float64 `json:"l"`
Buy float64 `json:"b"`
Sell float64 `json:"k"`
Change float64 `json:"c"`
Time int64 `json:"t"`
}

type priceResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data Price `json:"data"`
Code int `json:"code"`
Method string `json:"method"`
Message string `json:"message"`
Result struct {
Data Price `json:"data"`
} `json:"result"`
}
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var (
pairPtr = flag.String("pair", "", "market pair, example: MCO/USDT")
percentPtr = flag.Float64("percent", 0.00, "stop loss percent, example: 3.0 (3%)")
intervalPtr = flag.Int("interval", 30, "interval in seconds to update price, example: 30 (30 sec.)")
amountPtr = flag.String("amount", "", "(optional) amount to sell on stoploss")
amountPtr = flag.Float64("amount", 0, "(optional) amount to sell on stoploss")
chatPtr = flag.Int64("telegram.chat", 0, "(optional) telegram Chat ID for notify")
)

Expand All @@ -32,7 +32,7 @@ func main() {
log.Fatal("pair, percent parameters are required")
}

pair := strings.Split(strings.ToLower(*pairPtr), "/")
pair := strings.Split(strings.ToUpper(*pairPtr), "/")
api := cryptoCom.NewAPI(apiKey, secret)
notify := stoploss.NewNotify(os.Getenv("TELEGRAM_TOKEN"), *chatPtr)
trailing := stoploss.NewTrailing(stoploss.NewExchange(api), notify, pair[0], pair[1], *percentPtr/100, *amountPtr)
Expand Down
27 changes: 13 additions & 14 deletions stoploss/exchange.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package stoploss

import (
"strconv"
"strings"

cryptoCom "github.com/giansalex/crypto-com-trailing-stop-loss/crypto"
Expand All @@ -18,20 +17,20 @@ func NewExchange(api *cryptoCom.API) *Exchange {
}

// GetBalance get balance for coin
func (exchange *Exchange) GetBalance(coin string) (string, error) {
balances, err := exchange.api.GetBalance()
func (exchange *Exchange) GetBalance(coin string) (float64, error) {
coin = strings.ToUpper(coin)
balances, err := exchange.api.GetBalance(coin)
if err != nil {
return "0", err
return 0, err
}

coin = strings.ToLower(coin)
for _, balance := range balances {
if strings.ToLower(balance.Coin) == coin {
return balance.Normal, nil
if strings.ToUpper(balance.Currency) == coin {
return balance.Available, nil
}
}

return "0", nil
return 0, nil
}

// GetMarketPrice get last price for market pair
Expand All @@ -41,16 +40,16 @@ func (exchange *Exchange) GetMarketPrice(market string) (float64, error) {
return 0, err
}

return strconv.ParseFloat(price.Last, 64)
return price.Last, nil
}

// Sell create a sell order to market price
func (exchange *Exchange) Sell(market string, quantity string) (int, error) {
func (exchange *Exchange) Sell(market string, quantity float64) (string, error) {
order := cryptoCom.Order{
Side: "SELL",
Symbol: market,
Type: "2", // type=2: Market Price
Volume: quantity,
Side: "SELL",
Symbol: market,
Type: "MARKET",
Quantity: quantity,
}

return exchange.api.CreateOrder(order)
Expand Down
Loading

0 comments on commit 872bc2c

Please sign in to comment.