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") || strings.Contains(err.Error(), "Set the API KEY and API SECRET")
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) TestCredentials(apiKey, apiSecret string) error {
56 client, _ := poloniex.NewClient(apiKey, apiSecret)
58 _, err := client.TradeReturnDepositAdresses()
60 if poloniexInvalidCredentialsError(err) {
61 return utils.Error{InvalidCredentials, "invalid poloniex credentials"}
64 if poloniexRestrictedIPError(err) {
65 return utils.Error{IPRestricted, "IP restricted api key"}
71 func (p *Poloniex) GetBalance(apiKey, apiSecret string) (Summary, error) {
72 client, _ := poloniex.NewClient(apiKey, apiSecret)
75 accounts, err := client.TradeReturnAvailableAccountBalances()
76 if poloniexInvalidCredentialsError(err) {
77 return Summary{}, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
80 if poloniexRestrictedIPError(err) {
81 return Summary{}, utils.Error{IPRestricted, "IP restricted api key"}
88 positions, err := client.TradeGetMarginPosition()
93 marginAccount, err := client.TradeReturnMarginAccountSummary()
98 summary.Balances = make(map[string]Balance)
99 for currency, amount := range accounts.Exchange {
100 balance := summary.Balances[currency]
101 balance.Amount = balance.Amount.Add(amount)
103 summary.Balances[currency] = balance
106 summary.BTCValue, err = p.ComputeAccountBalanceValue(summary.Balances)
108 return Summary{}, err
111 for currencyPair, position := range positions {
112 if position.Type == "none" {
115 currency := currencyPair[4:]
116 balance := summary.Balances[currency]
117 balance.Amount = balance.Amount.Add(position.Amount)
118 balance.BTCValue = balance.BTCValue.Add(position.Total.Add(position.PlusValue))
120 summary.Balances[currency] = balance
123 summary.BTCValue = summary.BTCValue.Add(marginAccount.NetValue)
128 func (p *Poloniex) ComputeAccountBalanceValue(account map[string]Balance) (decimal.Decimal, error) {
129 var total decimal.Decimal
131 for currency, balance := range account {
132 pair, err := p.GetCurrencyPair("BTC", currency)
134 return decimal.Zero, err
137 total = total.Add(balance.Amount.Mul(pair.Rate))
138 balance.BTCValue = balance.BTCValue.Add(balance.Amount.Mul(pair.Rate))
139 account[currency] = balance
145 func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
146 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
150 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
153 pair, ok := p.TickerCache[pairName]
155 pair, err = p.fetchTicker(curr1, curr2)
157 if utils.ErrIs(err, CurrencyPairNotInTicker) {
158 // try to invert an existing ticker.
159 pair, err = p.fetchTicker(curr2, curr1)
161 return CurrencyPair{}, err
164 return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
168 return CurrencyPair{}, err
175 func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
176 tickers, err := p.publicClient.PubReturnTickers()
178 return CurrencyPair{}, err
181 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
183 if ticker, ok := tickers[pairName]; ok {
184 pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
186 if p.updateTickerChan != nil {
187 p.updateTickerChan <- pair
193 return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
196 func (p *Poloniex) StartTicker() error {
197 stream, err := poloniex.NewWSClient()
202 err = stream.SubscribeTicker()
207 p.updateTickerChan = make(chan CurrencyPair)
212 case data, ok := <-stream.Subs["ticker"]:
216 ticker := data.(poloniex.WSTicker)
217 if ticker.CurrencyPair == "USDT_BTC" || true {
219 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
222 case pair, ok := <-p.updateTickerChan:
226 p.TickerCache[pair.Name] = pair
230 p.updateTickerChan = nil