aboutsummaryrefslogblamecommitdiff
path: root/markets/poloniex.go
blob: cadc8295c2464c6dedbab9306671eeea4ae7a031 (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") || strings.Contains(err.Error(), "Set the API KEY and API SECRET")
}

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) TestCredentials(apiKey, apiSecret string) error {
	client, _ := poloniex.NewClient(apiKey, apiSecret)

	_, err := client.TradeReturnDepositAdresses()

	if poloniexInvalidCredentialsError(err) {
		return utils.Error{InvalidCredentials, "invalid poloniex credentials"}
	}

	if poloniexRestrictedIPError(err) {
		return utils.Error{IPRestricted, "IP restricted api key"}
	}

	return err
}

func (p *Poloniex) GetBalance(apiKey, apiSecret string) (Summary, error) {
	client, _ := poloniex.NewClient(apiKey, apiSecret)
	var summary Summary

	accounts, err := client.TradeReturnAvailableAccountBalances()
	if poloniexInvalidCredentialsError(err) {
		return Summary{}, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
	}

	if poloniexRestrictedIPError(err) {
		return Summary{}, utils.Error{IPRestricted, "IP restricted api key"}
	}

	if err != nil {
		return Summary{}, err
	}

	positions, err := client.TradeGetMarginPosition()
	if err != nil {
		return Summary{}, err
	}

	marginAccount, err := client.TradeReturnMarginAccountSummary()
	if err != nil {
		return Summary{}, err
	}

	summary.Balances = make(map[string]Balance)
	for currency, amount := range accounts.Exchange {
		balance := summary.Balances[currency]
		balance.Amount = balance.Amount.Add(amount)

		summary.Balances[currency] = balance
	}

	summary.BTCValue, err = p.ComputeAccountBalanceValue(summary.Balances)
	if err != nil {
		return Summary{}, err
	}

	for currencyPair, position := range positions {
		if position.Type == "none" {
			continue
		}
		currency := currencyPair[4:]
		balance := summary.Balances[currency]
		balance.Amount = balance.Amount.Add(position.Amount)
		balance.BTCValue = balance.BTCValue.Add(position.Total.Add(position.PlusValue))

		summary.Balances[currency] = balance
	}

	summary.BTCValue = summary.BTCValue.Add(marginAccount.NetValue)

	return summary, nil
}

func (p *Poloniex) ComputeAccountBalanceValue(account map[string]Balance) (decimal.Decimal, error) {
	var total decimal.Decimal

	for currency, balance := range account {
		pair, err := p.GetCurrencyPair("BTC", currency)
		if err != nil {
			return decimal.Zero, err
		}

		total = total.Add(balance.Amount.Mul(pair.Rate))
		balance.BTCValue = balance.BTCValue.Add(balance.Amount.Mul(pair.Rate))
		account[currency] = balance
	}

	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
}