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 const defaultTimeout = 10
21 func poloniexInvalidCredentialsError(err error) bool {
25 return strings.Contains(err.Error(), "Invalid API key/secret pair") || strings.Contains(err.Error(), "Set the API KEY and API SECRET")
28 func poloniexRestrictedIPError(err error) bool {
32 return strings.Contains(err.Error(), "Permission denied")
35 type CurrencyPair struct {
40 type Poloniex struct {
41 TickerCache map[string]CurrencyPair
43 publicClient *poloniex.Poloniex
44 updateTickerChan chan CurrencyPair
47 func NewPoloniex() *Poloniex {
48 client, _ := poloniex.NewClient("", "", defaultTimeout)
51 TickerCache: make(map[string]CurrencyPair),
52 updateTickerChan: nil,
57 func (p *Poloniex) TestCredentials(apiKey, apiSecret string, timeout int32) error {
58 client, _ := poloniex.NewClient(apiKey, apiSecret, timeout)
60 _, err := client.TradeReturnDepositAdresses()
62 if poloniexInvalidCredentialsError(err) {
63 return utils.Error{InvalidCredentials, "invalid poloniex credentials"}
66 if poloniexRestrictedIPError(err) {
67 return utils.Error{IPRestricted, "IP restricted api key"}
73 func (p *Poloniex) GetBalance(apiKey, apiSecret string, timeout int32) (Summary, error) {
74 client, _ := poloniex.NewClient(apiKey, apiSecret, timeout)
77 accounts, err := client.TradeReturnAvailableAccountBalances()
78 if poloniexInvalidCredentialsError(err) {
79 return Summary{}, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
82 if poloniexRestrictedIPError(err) {
83 return Summary{}, utils.Error{IPRestricted, "IP restricted api key"}
90 positions, err := client.TradeGetMarginPosition()
95 marginAccount, err := client.TradeReturnMarginAccountSummary()
100 summary.Balances = make(map[string]Balance)
101 for currency, amount := range accounts.Exchange {
102 balance := summary.Balances[currency]
103 balance.Amount = balance.Amount.Add(amount)
105 summary.Balances[currency] = balance
108 summary.BTCValue, err = p.ComputeAccountBalanceValue(summary.Balances)
110 return Summary{}, err
113 for currencyPair, position := range positions {
114 if position.Type == "none" {
117 currency := currencyPair[4:]
118 balance := summary.Balances[currency]
119 balance.Amount = balance.Amount.Add(position.Amount)
120 balance.BTCValue = balance.BTCValue.Add(position.Total.Add(position.PlusValue))
122 summary.Balances[currency] = balance
125 summary.BTCValue = summary.BTCValue.Add(marginAccount.NetValue)
130 func (p *Poloniex) ComputeAccountBalanceValue(account map[string]Balance) (decimal.Decimal, error) {
131 var total decimal.Decimal
133 for currency, balance := range account {
134 pair, err := p.GetCurrencyPair("BTC", currency)
136 return decimal.Zero, err
139 total = total.Add(balance.Amount.Mul(pair.Rate))
140 balance.BTCValue = balance.BTCValue.Add(balance.Amount.Mul(pair.Rate))
141 account[currency] = balance
147 func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
148 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
152 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
155 pair, ok := p.TickerCache[pairName]
157 pair, err = p.fetchTicker(curr1, curr2)
159 if utils.ErrIs(err, CurrencyPairNotInTicker) {
160 // try to invert an existing ticker.
161 pair, err = p.fetchTicker(curr2, curr1)
163 return CurrencyPair{}, err
166 return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
170 return CurrencyPair{}, err
177 func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
178 tickers, err := p.publicClient.PubReturnTickers()
180 return CurrencyPair{}, err
183 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
185 if ticker, ok := tickers[pairName]; ok {
186 pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
188 if p.updateTickerChan != nil {
189 p.updateTickerChan <- pair
195 return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
198 func (p *Poloniex) StartTicker() error {
199 stream, err := poloniex.NewWSClient()
204 err = stream.SubscribeTicker()
209 p.updateTickerChan = make(chan CurrencyPair)
214 case data, ok := <-stream.Subs["ticker"]:
218 ticker := data.(poloniex.WSTicker)
219 if ticker.CurrencyPair == "USDT_BTC" || true {
221 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
224 case pair, ok := <-p.updateTickerChan:
228 p.TickerCache[pair.Name] = pair
232 p.updateTickerChan = nil