]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front.git/commitdiff
poloniex balance draft poloniex_balance
authorJLoup <jeanloup.jamet@gmail.com>
Tue, 20 Feb 2018 23:20:29 +0000 (00:20 +0100)
committerJLoup <jeanloup.jamet@gmail.com>
Tue, 20 Feb 2018 23:20:29 +0000 (00:20 +0100)
Gopkg.lock
Gopkg.toml
api/market_config.go
api/routes.go
cmd/web/js/api.js
markets/poloniex.go [new file with mode: 0644]

index 7f0f166efdfe3a28437c119a1cc5ba28a54969e0..88c2eda93a1a0c4b94bd9ad5288836d8023c368e 100644 (file)
   packages = ["."]
   revision = "1c35d901db3da928c72a72d8458480cc9ade058f"
 
+[[projects]]
+  branch = "master"
+  name = "github.com/jloup/poloniex"
+  packages = ["."]
+  revision = "e75e6fd7991c1d71576ad97de73fc922f24a5fd2"
+
 [[projects]]
   branch = "master"
   name = "github.com/jloup/utils"
   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"]
     "blowfish",
     "ssh/terminal"
   ]
-  revision = "650f4a345ab4e5b245a3034b110ebc7299e68186"
+  revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/net"
+  packages = ["websocket"]
+  revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb"
 
 [[projects]]
   branch = "master"
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "c9af022a586632799c6259f6c48eef8dad7080e36b96e8cb5cb905b316c4cb9b"
+  inputs-digest = "d3c9b3094ed174bcf1631e3a998a75d557c65c195d7a8fd5ca9912f71f334ce1"
   solver-name = "gps-cdcl"
   solver-version = 1
index f4686b5eae63b5146c819ea70ce84f74be8a403a..4feee4d8d35575ed61cbe41fa961058705f0db5a 100644 (file)
 [prune]
   go-tests = true
   unused-packages = true
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/jloup/poloniex"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/shopspring/decimal"
index 3fd10ae000d19fba89f6e86b4911a1dde21bf294..ce7918495cac8d9cb5c06144a13e82aab04e86f7 100644 (file)
@@ -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
index d7e712c7c5f0495717ef850e88695a401781037c..385db9a8e7d1ba2646ac34e2d0ce2fbe82c54324 100644 (file)
@@ -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{}
 
index e2acd1d31dd4b9ea3b0d5555770a32f9d87ec47a..5c19fdf613fd5d165ec17d75d0cb40222d92fe5c 100644 (file)
@@ -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 (file)
index 0000000..c55a3da
--- /dev/null
@@ -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
+}