+package markets
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jloup/poloniex"
+ "github.com/jloup/utils"
+ "github.com/shopspring/decimal"
+)
+
+var (
+ ErrorFlagCounter utils.Counter = 0
+ CurrencyPairNotInTicker = utils.InitFlag(&ErrorFlagCounter, "CurrencyPairNotInTicker")
+ InvalidCredentials = utils.InitFlag(&ErrorFlagCounter, "InvalidCredentials")
+)
+
+func poloniexInvalidCredentialsError(err error) bool {
+ if err == nil {
+ return false
+ }
+ return strings.Contains(err.Error(), "Invalid API key/secret pair")
+}
+
+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 string) (map[string]decimal.Decimal, error) {
+ client, _ := poloniex.NewClient(apiKey, apiSecret)
+
+ accounts, err := client.TradeReturnAvailableAccountBalances()
+ if poloniexInvalidCredentialsError(err) {
+ return nil, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ balances := make(map[string]decimal.Decimal)
+ for currency, balance := range accounts.Margin {
+ balances[currency] = balances[currency].Add(balance)
+ }
+
+ for currency, balance := range accounts.Exchange {
+ balances[currency] = balances[currency].Add(balance)
+ }
+
+ return balances, nil
+}
+
+func (p *Poloniex) ComputeAccountBalanceValue(account map[string]decimal.Decimal, baseCurrency string) (decimal.Decimal, error) {
+ var total decimal.Decimal
+
+ for currency, amount := range account {
+ pair, err := p.GetCurrencyPair(baseCurrency, 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)
+ var err error
+
+ if curr1 == curr2 {
+ return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
+ }
+
+ pair, ok := p.TickerCache[pairName]
+ if !ok {
+ pair, err = p.fetchTicker(curr1, curr2)
+
+ if utils.ErrIs(err, CurrencyPairNotInTicker) {
+ // try to invert an existing ticker.
+ pair, err = p.fetchTicker(curr2, curr1)
+ if err != nil {
+ return CurrencyPair{}, err
+ }
+
+ return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
+ }
+
+ if err != nil {
+ return CurrencyPair{}, err
+ }
+ }
+
+ return pair, nil
+}
+
+func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
+ tickers, err := p.publicClient.PubReturnTickers()
+ if err != nil {
+ return CurrencyPair{}, err
+ }
+
+ pairName := fmt.Sprintf("%s_%s", curr1, curr2)
+
+ if ticker, ok := tickers[pairName]; ok {
+ pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
+
+ if p.updateTickerChan != nil {
+ p.updateTickerChan <- pair
+ }
+
+ return pair, nil
+ }
+
+ return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
+}
+
+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)
+
+ for {
+ quit := false
+ select {
+ case data, ok := <-stream.Subs["ticker"]:
+ if !ok {
+ quit = true
+ } else {
+ ticker := data.(poloniex.WSTicker)
+ if ticker.CurrencyPair == "USDT_BTC" || true {
+ }
+ 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 {
+ p.updateTickerChan = nil
+ break
+ }
+ }
+
+ return nil
+}