7 "github.com/jloup/poloniex"
8 "github.com/jloup/utils"
9 "github.com/shopspring/decimal"
13 ErrorFlagCounter utils.Counter = 0
14 CurrencyPairNotInTicker = utils.InitFlag(&ErrorFlagCounter, "CurrencyPairNotInTicker")
15 InvalidCredentials = utils.InitFlag(&ErrorFlagCounter, "InvalidCredentials")
18 func poloniexInvalidCredentialsError(err error) bool {
22 return strings.Contains(err.Error(), "Invalid API key/secret pair")
25 type CurrencyPair struct {
30 type Poloniex struct {
31 TickerCache map[string]CurrencyPair
33 publicClient *poloniex.Poloniex
34 updateTickerChan chan CurrencyPair
37 func NewPoloniex() *Poloniex {
38 client, _ := poloniex.NewClient("", "")
41 TickerCache: make(map[string]CurrencyPair),
42 updateTickerChan: nil,
47 func (p *Poloniex) GetBalance(apiKey, apiSecret string) (map[string]decimal.Decimal, error) {
48 client, _ := poloniex.NewClient(apiKey, apiSecret)
50 accounts, err := client.TradeReturnAvailableAccountBalances()
51 if poloniexInvalidCredentialsError(err) {
52 return nil, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
59 balances := make(map[string]decimal.Decimal)
60 for currency, balance := range accounts.Margin {
61 balances[currency] = balances[currency].Add(balance)
64 for currency, balance := range accounts.Exchange {
65 balances[currency] = balances[currency].Add(balance)
71 func (p *Poloniex) ComputeAccountBalanceValue(account map[string]decimal.Decimal, baseCurrency string) (decimal.Decimal, error) {
72 var total decimal.Decimal
74 for currency, amount := range account {
75 pair, err := p.GetCurrencyPair(baseCurrency, currency)
77 return decimal.Zero, err
80 total = total.Add(amount.Mul(pair.Rate))
86 func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
87 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
91 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
94 pair, ok := p.TickerCache[pairName]
96 pair, err = p.fetchTicker(curr1, curr2)
98 if utils.ErrIs(err, CurrencyPairNotInTicker) {
99 // try to invert an existing ticker.
100 pair, err = p.fetchTicker(curr2, curr1)
102 return CurrencyPair{}, err
105 return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
109 return CurrencyPair{}, err
116 func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
117 tickers, err := p.publicClient.PubReturnTickers()
119 return CurrencyPair{}, err
122 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
124 if ticker, ok := tickers[pairName]; ok {
125 pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
127 if p.updateTickerChan != nil {
128 p.updateTickerChan <- pair
134 return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
137 func (p *Poloniex) StartTicker() error {
138 stream, err := poloniex.NewWSClient()
143 err = stream.SubscribeTicker()
148 p.updateTickerChan = make(chan CurrencyPair)
153 case data, ok := <-stream.Subs["ticker"]:
157 ticker := data.(poloniex.WSTicker)
158 if ticker.CurrencyPair == "USDT_BTC" || true {
160 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
163 case pair, ok := <-p.updateTickerChan:
167 p.TickerCache[pair.Name] = pair
171 p.updateTickerChan = nil