From 6c5c1403578a2b36038fd94f6d1edce9ae62205b Mon Sep 17 00:00:00 2001 From: JLoup Date: Wed, 21 Feb 2018 00:20:29 +0100 Subject: [PATCH] poloniex balance draft --- Gopkg.lock | 22 ++++++- Gopkg.toml | 8 +++ api/market_config.go | 52 +++++++++++++++ api/routes.go | 11 ++++ cmd/web/js/api.js | 11 ++++ markets/poloniex.go | 149 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 markets/poloniex.go diff --git a/Gopkg.lock b/Gopkg.lock index 7f0f166..88c2eda 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -76,6 +76,12 @@ packages = ["."] revision = "1c35d901db3da928c72a72d8458480cc9ade058f" +[[projects]] + branch = "master" + name = "github.com/jloup/poloniex" + packages = ["."] + revision = "e75e6fd7991c1d71576ad97de73fc922f24a5fd2" + [[projects]] branch = "master" name = "github.com/jloup/utils" @@ -98,6 +104,12 @@ revision = "b7b89250c468c06871d3837bee02e2d5c155ae19" version = "v1.0.0" +[[projects]] + branch = "master" + name = "github.com/shopspring/decimal" + packages = ["."] + revision = "e3482495ff4cba75613e4177ed79825c890058a9" + [[projects]] name = "github.com/ugorji/go" packages = ["codec"] @@ -112,7 +124,13 @@ "blowfish", "ssh/terminal" ] - revision = "650f4a345ab4e5b245a3034b110ebc7299e68186" + revision = "432090b8f568c018896cd8a0fb0345872bbac6ce" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["websocket"] + revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb" [[projects]] branch = "master" @@ -138,6 +156,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c9af022a586632799c6259f6c48eef8dad7080e36b96e8cb5cb905b316c4cb9b" + inputs-digest = "d3c9b3094ed174bcf1631e3a998a75d557c65c195d7a8fd5ca9912f71f334ce1" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index f4686b5..4feee4d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -56,3 +56,11 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + branch = "master" + name = "github.com/jloup/poloniex" + +[[constraint]] + branch = "master" + name = "github.com/shopspring/decimal" diff --git a/api/market_config.go b/api/market_config.go index 3fd10ae..ce79184 100644 --- a/api/market_config.go +++ b/api/market_config.go @@ -3,7 +3,9 @@ package api import ( "fmt" + "github.com/shopspring/decimal" "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" + "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/markets" ) type MarketConfigQuery struct { @@ -42,6 +44,56 @@ func (q MarketConfigQuery) Run() (interface{}, *Error) { return config.Config, nil } +type MarketBalanceQuery struct { + In struct { + User db.User + Market string + Currency string + } +} + +var Poloniex *markets.Poloniex + +func init() { + Poloniex = markets.NewPoloniex() + Poloniex.StartTicker() +} + +func (q MarketBalanceQuery) ValidateParams() *Error { + if q.In.Market != "poloniex" { + return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)} + } + + // TODO: we should request market for available currencies. + if q.In.Currency != "BTC" || q.In.Currency != "USDT" { + return &Error{BadRequest, "invalid currency, accept [BTC, USDT]", fmt.Errorf("'%v' is not a valid currency", q.In.Currency)} + } + + return nil +} + +func (q MarketBalanceQuery) Run() (interface{}, *Error) { + config, err := db.GetUserMarketConfig(q.In.User.Id, q.In.Market) + if err != nil { + return nil, NewInternalError(err) + } + + if config.Config["key"] == "" || config.Config["secret"] == "" { + return nil, &Error{BadRequest, "your credentials for this market are not setup", fmt.Errorf("'%v' credentials are not setup", q.In.Market)} + } + + balance, err := Poloniex.GetBalance(config.Config["key"], config.Config["secret"], q.In.Currency) + if err != nil { + return nil, NewInternalError(err) + } + + result := struct { + Balance decimal.Decimal `json:balance` + }{balance} + + return &result, nil +} + type UpdateMarketConfigQuery struct { In struct { User db.User diff --git a/api/routes.go b/api/routes.go index d7e712c..385db9a 100644 --- a/api/routes.go +++ b/api/routes.go @@ -41,6 +41,7 @@ var Groups = []Group{ []Route{ {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"}, {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"}, + {"GET", []gin.HandlerFunc{GetMarketBalance}, "/:name/balance"}, }, }, } @@ -111,6 +112,16 @@ func GetMarketConfig(c *gin.Context) { RunQuery(query, c) } +func GetMarketBalance(c *gin.Context) { + query := &MarketBalanceQuery{} + + query.In.User = GetUser(c) + query.In.Market = c.Param("name") + query.In.Currency = "BTC" + + RunQuery(query, c) +} + func UpdateMarketConfig(c *gin.Context) { query := &UpdateMarketConfigQuery{} diff --git a/cmd/web/js/api.js b/cmd/web/js/api.js index e2acd1d..5c19fdf 100644 --- a/cmd/web/js/api.js +++ b/cmd/web/js/api.js @@ -53,6 +53,17 @@ var ApiEndpoints = { return '/market/' + params.name; } }, + 'MARKET_BALANCE': { + 'type': 'GET', + 'auth': true, + 'parameters': [ + {'name': 'name', 'mandatory': true, 'inquery': false}, + {'name': 'currency', 'mandatory': true, 'inquery': true}, + ], + 'buildUrl': function(params) { + return '/market/' + params.name + '/balance'; + } + }, 'UPDATE_MARKET': { 'type': 'POST', 'auth': true, diff --git a/markets/poloniex.go b/markets/poloniex.go new file mode 100644 index 0000000..c55a3da --- /dev/null +++ b/markets/poloniex.go @@ -0,0 +1,149 @@ +package markets + +import ( + "fmt" + + "github.com/jloup/poloniex" + "github.com/shopspring/decimal" +) + +type CurrencyPair struct { + Name string + Rate decimal.Decimal +} + +type Poloniex struct { + TickerCache map[string]CurrencyPair + + publicClient *poloniex.Poloniex + updateTickerChan chan CurrencyPair +} + +func NewPoloniex() *Poloniex { + client, _ := poloniex.NewClient("", "") + + return &Poloniex{ + TickerCache: make(map[string]CurrencyPair), + updateTickerChan: nil, + publicClient: client, + } +} + +func (p *Poloniex) GetBalance(apiKey, apiSecret, base_currency string) (decimal.Decimal, error) { + client, _ := poloniex.NewClient(apiKey, apiSecret) + + accounts, err := client.TradeReturnAvailableAccountBalances() + if err != nil { + return decimal.Zero, err + } + + marginBalance, err := p.computeAccountBalance(accounts.Margin, base_currency) + if err != nil { + return decimal.Zero, err + } + + exchangeBalance, err := p.computeAccountBalance(accounts.Exchange, base_currency) + if err != nil { + return decimal.Zero, err + } + + return decimal.Sum(marginBalance, exchangeBalance), nil +} + +func (p *Poloniex) computeAccountBalance(account map[string]decimal.Decimal, base_currency string) (decimal.Decimal, error) { + var total decimal.Decimal + + for currency, amount := range account { + pair, err := p.GetCurrencyPair(base_currency, currency) + if err != nil { + return decimal.Zero, err + } + + total = total.Add(amount.Mul(pair.Rate)) + } + + return total, nil +} + +func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) { + pairName := fmt.Sprintf("%s_%s", curr1, curr2) + + fmt.Println(pairName) + if curr1 == curr2 { + return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil + } + + pair, ok := p.TickerCache[pairName] + if !ok { + pair, err := p.fetchTicker(pairName) + if err != nil { + return CurrencyPair{}, err + } + + return pair, nil + } + + return pair, nil +} + +func (p *Poloniex) fetchTicker(pair string) (CurrencyPair, error) { + tickers, err := p.publicClient.PubReturnTickers() + if err != nil { + return CurrencyPair{}, err + } + + if ticker, ok := tickers[pair]; ok { + pair := CurrencyPair{Name: pair, Rate: ticker.Last} + + if p.updateTickerChan != nil { + p.updateTickerChan <- pair + } + + return pair, nil + } else { + return CurrencyPair{}, fmt.Errorf("pair '%v' not in tickers", pair) + } +} + +func (p *Poloniex) StartTicker() error { + stream, err := poloniex.NewWSClient() + if err != nil { + return err + } + + err = stream.SubscribeTicker() + if err != nil { + return err + } + + p.updateTickerChan = make(chan CurrencyPair) + + go func() { + for { + quit := false + select { + case data, ok := <-stream.Subs["ticker"]: + if !ok { + quit = true + } else { + ticker := data.(poloniex.WSTicker) + p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)} + } + + case pair, ok := <-p.updateTickerChan: + if !ok { + quit = true + } else { + p.TickerCache[pair.Name] = pair + } + } + if quit { + //TODO: logit + p.updateTickerChan = nil + break + } + } + }() + + return nil +} -- 2.41.0