aboutsummaryrefslogblamecommitdiff
path: root/markets/poloniex.go
blob: 34ebb7eaf5e296bd9d550118d18428b18ccd4155 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                                                            
                                                                                                 








                                                                           






                                                                 
























































































































































                                                                                                                                                     
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")
	IPRestricted                          = utils.InitFlag(&ErrorFlagCounter, "IPRestricted")
)

func poloniexInvalidCredentialsError(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(err.Error(), "Invalid API key/secret pair")
}

func poloniexRestrictedIPError(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(err.Error(), "Permission denied")
}

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
}