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 }