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