]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front.git/blob - markets/poloniex.go
Refactor Portfolio balance.
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front.git] / markets / poloniex.go
1 package markets
2
3 import (
4 "fmt"
5 "strings"
6
7 "github.com/jloup/poloniex"
8 "github.com/jloup/utils"
9 "github.com/shopspring/decimal"
10 )
11
12 var (
13 ErrorFlagCounter utils.Counter = 0
14 CurrencyPairNotInTicker = utils.InitFlag(&ErrorFlagCounter, "CurrencyPairNotInTicker")
15 InvalidCredentials = utils.InitFlag(&ErrorFlagCounter, "InvalidCredentials")
16 IPRestricted = utils.InitFlag(&ErrorFlagCounter, "IPRestricted")
17 )
18
19 func poloniexInvalidCredentialsError(err error) bool {
20 if err == nil {
21 return false
22 }
23 return strings.Contains(err.Error(), "Invalid API key/secret pair")
24 }
25
26 func poloniexRestrictedIPError(err error) bool {
27 if err == nil {
28 return false
29 }
30 return strings.Contains(err.Error(), "Permission denied")
31 }
32
33 type CurrencyPair struct {
34 Name string
35 Rate decimal.Decimal
36 }
37
38 type Poloniex struct {
39 TickerCache map[string]CurrencyPair
40
41 publicClient *poloniex.Poloniex
42 updateTickerChan chan CurrencyPair
43 }
44
45 func NewPoloniex() *Poloniex {
46 client, _ := poloniex.NewClient("", "")
47
48 return &Poloniex{
49 TickerCache: make(map[string]CurrencyPair),
50 updateTickerChan: nil,
51 publicClient: client,
52 }
53 }
54
55 func (p *Poloniex) TestCredentials(apiKey, apiSecret string) error {
56 client, _ := poloniex.NewClient(apiKey, apiSecret)
57
58 _, err := client.TradeReturnDepositAdresses()
59
60 if poloniexInvalidCredentialsError(err) {
61 return utils.Error{InvalidCredentials, "invalid poloniex credentials"}
62 }
63
64 if poloniexRestrictedIPError(err) {
65 return utils.Error{IPRestricted, "IP restricted api key"}
66 }
67
68 return nil
69 }
70
71 func (p *Poloniex) GetBalance(apiKey, apiSecret string) (Summary, error) {
72 client, _ := poloniex.NewClient(apiKey, apiSecret)
73 var summary Summary
74
75 accounts, err := client.TradeReturnAvailableAccountBalances()
76 if poloniexInvalidCredentialsError(err) {
77 return Summary{}, utils.Error{InvalidCredentials, "invalid poloniex credentials"}
78 }
79
80 if poloniexRestrictedIPError(err) {
81 return Summary{}, utils.Error{IPRestricted, "IP restricted api key"}
82 }
83
84 if err != nil {
85 return Summary{}, err
86 }
87
88 positions, err := client.TradeGetMarginPosition()
89 if err != nil {
90 return Summary{}, err
91 }
92
93 marginAccount, err := client.TradeReturnMarginAccountSummary()
94 if err != nil {
95 return Summary{}, err
96 }
97
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)
102
103 summary.Balances[currency] = balance
104 }
105
106 summary.BTCValue, err = p.ComputeAccountBalanceValue(summary.Balances)
107 if err != nil {
108 return Summary{}, err
109 }
110
111 for currencyPair, position := range positions {
112 if position.Type == "none" {
113 continue
114 }
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))
119
120 summary.Balances[currency] = balance
121 }
122
123 summary.BTCValue = summary.BTCValue.Add(marginAccount.NetValue)
124
125 return summary, nil
126 }
127
128 func (p *Poloniex) ComputeAccountBalanceValue(account map[string]Balance) (decimal.Decimal, error) {
129 var total decimal.Decimal
130
131 for currency, balance := range account {
132 pair, err := p.GetCurrencyPair("BTC", currency)
133 if err != nil {
134 return decimal.Zero, err
135 }
136
137 total = total.Add(balance.Amount.Mul(pair.Rate))
138 balance.BTCValue = balance.BTCValue.Add(balance.Amount.Mul(pair.Rate))
139 account[currency] = balance
140 }
141
142 return total, nil
143 }
144
145 func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
146 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
147 var err error
148
149 if curr1 == curr2 {
150 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
151 }
152
153 pair, ok := p.TickerCache[pairName]
154 if !ok {
155 pair, err = p.fetchTicker(curr1, curr2)
156
157 if utils.ErrIs(err, CurrencyPairNotInTicker) {
158 // try to invert an existing ticker.
159 pair, err = p.fetchTicker(curr2, curr1)
160 if err != nil {
161 return CurrencyPair{}, err
162 }
163
164 return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil
165 }
166
167 if err != nil {
168 return CurrencyPair{}, err
169 }
170 }
171
172 return pair, nil
173 }
174
175 func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) {
176 tickers, err := p.publicClient.PubReturnTickers()
177 if err != nil {
178 return CurrencyPair{}, err
179 }
180
181 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
182
183 if ticker, ok := tickers[pairName]; ok {
184 pair := CurrencyPair{Name: pairName, Rate: ticker.Last}
185
186 if p.updateTickerChan != nil {
187 p.updateTickerChan <- pair
188 }
189
190 return pair, nil
191 }
192
193 return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)}
194 }
195
196 func (p *Poloniex) StartTicker() error {
197 stream, err := poloniex.NewWSClient()
198 if err != nil {
199 return err
200 }
201
202 err = stream.SubscribeTicker()
203 if err != nil {
204 return err
205 }
206
207 p.updateTickerChan = make(chan CurrencyPair)
208
209 for {
210 quit := false
211 select {
212 case data, ok := <-stream.Subs["ticker"]:
213 if !ok {
214 quit = true
215 } else {
216 ticker := data.(poloniex.WSTicker)
217 if ticker.CurrencyPair == "USDT_BTC" || true {
218 }
219 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
220 }
221
222 case pair, ok := <-p.updateTickerChan:
223 if !ok {
224 quit = true
225 } else {
226 p.TickerCache[pair.Name] = pair
227 }
228 }
229 if quit {
230 p.updateTickerChan = nil
231 break
232 }
233 }
234
235 return nil
236 }