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
}