diff options
Diffstat (limited to 'markets')
-rw-r--r-- | markets/poloniex.go | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/markets/poloniex.go b/markets/poloniex.go new file mode 100644 index 0000000..5e1ec64 --- /dev/null +++ b/markets/poloniex.go | |||
@@ -0,0 +1,177 @@ | |||
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 | ) | ||
17 | |||
18 | func poloniexInvalidCredentialsError(err error) bool { | ||
19 | if err == nil { | ||
20 | return false | ||
21 | } | ||
22 | return strings.Contains(err.Error(), "Invalid API key/secret pair") | ||
23 | } | ||
24 | |||
25 | type CurrencyPair struct { | ||
26 | Name string | ||
27 | Rate decimal.Decimal | ||
28 | } | ||
29 | |||
30 | type Poloniex struct { | ||
31 | TickerCache map[string]CurrencyPair | ||
32 | |||
33 | publicClient *poloniex.Poloniex | ||
34 | updateTickerChan chan CurrencyPair | ||
35 | } | ||
36 | |||
37 | func NewPoloniex() *Poloniex { | ||
38 | client, _ := poloniex.NewClient("", "") | ||
39 | |||
40 | return &Poloniex{ | ||
41 | TickerCache: make(map[string]CurrencyPair), | ||
42 | updateTickerChan: nil, | ||
43 | publicClient: client, | ||
44 | } | ||
45 | } | ||
46 | |||
47 | func (p *Poloniex) GetBalance(apiKey, apiSecret string) (map[string]decimal.Decimal, error) { | ||
48 | client, _ := poloniex.NewClient(apiKey, apiSecret) | ||
49 | |||
50 | accounts, err := client.TradeReturnAvailableAccountBalances() | ||
51 | if poloniexInvalidCredentialsError(err) { | ||
52 | return nil, utils.Error{InvalidCredentials, "invalid poloniex credentials"} | ||
53 | } | ||
54 | |||
55 | if err != nil { | ||
56 | return nil, err | ||
57 | } | ||
58 | |||
59 | balances := make(map[string]decimal.Decimal) | ||
60 | for currency, balance := range accounts.Margin { | ||
61 | balances[currency] = balances[currency].Add(balance) | ||
62 | } | ||
63 | |||
64 | for currency, balance := range accounts.Exchange { | ||
65 | balances[currency] = balances[currency].Add(balance) | ||
66 | } | ||
67 | |||
68 | return balances, nil | ||
69 | } | ||
70 | |||
71 | func (p *Poloniex) ComputeAccountBalanceValue(account map[string]decimal.Decimal, baseCurrency string) (decimal.Decimal, error) { | ||
72 | var total decimal.Decimal | ||
73 | |||
74 | for currency, amount := range account { | ||
75 | pair, err := p.GetCurrencyPair(baseCurrency, currency) | ||
76 | if err != nil { | ||
77 | return decimal.Zero, err | ||
78 | } | ||
79 | |||
80 | total = total.Add(amount.Mul(pair.Rate)) | ||
81 | } | ||
82 | |||
83 | return total, nil | ||
84 | } | ||
85 | |||
86 | func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) { | ||
87 | pairName := fmt.Sprintf("%s_%s", curr1, curr2) | ||
88 | var err error | ||
89 | |||
90 | if curr1 == curr2 { | ||
91 | return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil | ||
92 | } | ||
93 | |||
94 | pair, ok := p.TickerCache[pairName] | ||
95 | if !ok { | ||
96 | pair, err = p.fetchTicker(curr1, curr2) | ||
97 | |||
98 | if utils.ErrIs(err, CurrencyPairNotInTicker) { | ||
99 | // try to invert an existing ticker. | ||
100 | pair, err = p.fetchTicker(curr2, curr1) | ||
101 | if err != nil { | ||
102 | return CurrencyPair{}, err | ||
103 | } | ||
104 | |||
105 | return CurrencyPair{pairName, decimal.NewFromFloat(1.0).Div(pair.Rate)}, nil | ||
106 | } | ||
107 | |||
108 | if err != nil { | ||
109 | return CurrencyPair{}, err | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return pair, nil | ||
114 | } | ||
115 | |||
116 | func (p *Poloniex) fetchTicker(curr1, curr2 string) (CurrencyPair, error) { | ||
117 | tickers, err := p.publicClient.PubReturnTickers() | ||
118 | if err != nil { | ||
119 | return CurrencyPair{}, err | ||
120 | } | ||
121 | |||
122 | pairName := fmt.Sprintf("%s_%s", curr1, curr2) | ||
123 | |||
124 | if ticker, ok := tickers[pairName]; ok { | ||
125 | pair := CurrencyPair{Name: pairName, Rate: ticker.Last} | ||
126 | |||
127 | if p.updateTickerChan != nil { | ||
128 | p.updateTickerChan <- pair | ||
129 | } | ||
130 | |||
131 | return pair, nil | ||
132 | } | ||
133 | |||
134 | return CurrencyPair{}, utils.Error{CurrencyPairNotInTicker, fmt.Sprintf("%s_%s not in ticker", curr1, curr2)} | ||
135 | } | ||
136 | |||
137 | func (p *Poloniex) StartTicker() error { | ||
138 | stream, err := poloniex.NewWSClient() | ||
139 | if err != nil { | ||
140 | return err | ||
141 | } | ||
142 | |||
143 | err = stream.SubscribeTicker() | ||
144 | if err != nil { | ||
145 | return err | ||
146 | } | ||
147 | |||
148 | p.updateTickerChan = make(chan CurrencyPair) | ||
149 | |||
150 | for { | ||
151 | quit := false | ||
152 | select { | ||
153 | case data, ok := <-stream.Subs["ticker"]: | ||
154 | if !ok { | ||
155 | quit = true | ||
156 | } else { | ||
157 | ticker := data.(poloniex.WSTicker) | ||
158 | if ticker.CurrencyPair == "USDT_BTC" || true { | ||
159 | } | ||
160 | p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)} | ||
161 | } | ||
162 | |||
163 | case pair, ok := <-p.updateTickerChan: | ||
164 | if !ok { | ||
165 | quit = true | ||
166 | } else { | ||
167 | p.TickerCache[pair.Name] = pair | ||
168 | } | ||
169 | } | ||
170 | if quit { | ||
171 | p.updateTickerChan = nil | ||
172 | break | ||
173 | } | ||
174 | } | ||
175 | |||
176 | return nil | ||
177 | } | ||