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")
16 IPRestricted = utils.InitFlag(&ErrorFlagCounter, "IPRestricted")
19 func poloniexInvalidCredentialsError(err error) bool {
23 return strings.Contains(err.Error(), "Invalid API key/secret pair")
26 func poloniexRestrictedIPError(err error) bool {
30 return strings.Contains(err.Error(), "Permission denied")
33 type CurrencyPair struct {
38 type Poloniex struct {
39 TickerCache map[string]CurrencyPair
41 publicClient *poloniex.Poloniex
42 updateTickerChan chan CurrencyPair
45 func NewPoloniex() *Poloniex {
46 client, _ := poloniex.NewClient("", "")
49 TickerCache: make(map[string]CurrencyPair),
50 updateTickerChan: nil,
55 func (p *Poloniex) GetBalance(apiKey, apiSecret string) (map[string]decimal.Decimal, error) {
56 client, _ := poloniex.NewClient(apiKey, apiSecret)
58 accounts, err := client.TradeReturnAvailableAccountBalances()
59 if poloniexInvalidCredentialsError(err) {
60 return nil, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
67 balances := make(map[string]decimal.Decimal)
68 for currency, balance := range accounts.Margin {
69 balances[currency] = balances[currency].Add(balance)
72 for currency, balance := range accounts.Exchange {
73 balances[currency] = balances[currency].Add(balance)
79 func (p *Poloniex) ComputeAccountBalanceValue(account map[string]decimal.Decimal, baseCurrency string) (decimal.Decimal, error) {
80 var total decimal.Decimal
82 for currency, amount := range account {
83 pair, err := p.GetCurrencyPair(baseCurrency, currency)
85 return decimal.Zero, err
88 total = total.Add(amount.Mul(pair.Rate))
94 func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
95 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
99 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
102 pair, ok := p.TickerCache[pairName]
104 pair, err = p.fetchTicker(curr1, curr2)
106 if utils.ErrIs(err, CurrencyPairNotInTicker) {
107 // try to invert an existing ticker.
108 pair, err = p.fetchTicker(curr2, curr1)
110 return CurrencyPair{}, err
113 return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
117 return CurrencyPair{}, err
124 func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
125 tickers, err := p.publicClient.PubReturnTickers()
127 return CurrencyPair{}, err
130 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
132 if ticker, ok := tickers[pairName]; ok {
133 pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
135 if p.updateTickerChan != nil {
136 p.updateTickerChan <- pair
142 return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
145 func (p *Poloniex) StartTicker() error {
146 stream, err := poloniex.NewWSClient()
151 err = stream.SubscribeTicker()
156 p.updateTickerChan = make(chan CurrencyPair)
161 case data, ok := <-stream.Subs["ticker"]:
165 ticker := data.(poloniex.WSTicker)
166 if ticker.CurrencyPair == "USDT_BTC" || true {
168 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
171 case pair, ok := <-p.updateTickerChan:
175 p.TickerCache[pair.Name] = pair
179 p.updateTickerChan = nil