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) 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 nil } 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 }