]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - tests/test_ccxt_wrapper.py
Merge branch 'dev'
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / tests / test_ccxt_wrapper.py
1 from .helper import limits, unittest, mock, D
2 import requests_mock
3 import market
4
5 @unittest.skipUnless("unit" in limits, "Unit skipped")
6 class poloniexETest(unittest.TestCase):
7 def setUp(self):
8 super().setUp()
9 self.wm = requests_mock.Mocker()
10 self.wm.start()
11
12 self.s = market.ccxt.poloniexE()
13
14 def tearDown(self):
15 self.wm.stop()
16 super().tearDown()
17
18 def test__init(self):
19 with self.subTest("Nominal case"), \
20 mock.patch("market.ccxt.poloniexE.session") as session:
21 session.request.return_value = "response"
22 ccxt = market.ccxt.poloniexE()
23 ccxt._market = mock.Mock
24 ccxt._market.report = mock.Mock()
25 ccxt._market.market_id = 3
26 ccxt._market.user_id = 3
27
28 ccxt.session.request("GET", "URL", data="data",
29 headers={})
30 ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
31 {'X-market-id': '3', 'X-user-id': '3'}, 'response')
32
33 with self.subTest("Raising"),\
34 mock.patch("market.ccxt.poloniexE.session") as session:
35 session.request.side_effect = market.ccxt.RequestException("Boo")
36
37 ccxt = market.ccxt.poloniexE()
38 ccxt._market = mock.Mock
39 ccxt._market.report = mock.Mock()
40 ccxt._market.market_id = 3
41 ccxt._market.user_id = 3
42
43 with self.assertRaises(market.ccxt.RequestException, msg="Boo") as cm:
44 ccxt.session.request("GET", "URL", data="data",
45 headers={})
46 ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
47 {'X-market-id': '3', 'X-user-id': '3'}, cm.exception)
48
49
50 def test_nanoseconds(self):
51 with mock.patch.object(market.ccxt.time, "time") as time:
52 time.return_value = 123456.7890123456
53 self.assertEqual(123456789012345, self.s.nanoseconds())
54
55 def test_nonce(self):
56 with mock.patch.object(market.ccxt.time, "time") as time:
57 time.return_value = 123456.7890123456
58 self.assertEqual(123456789012345, self.s.nonce())
59
60 def test_request(self):
61 with mock.patch.object(market.ccxt.poloniex, "request") as request,\
62 mock.patch("market.ccxt.retry_call") as retry_call:
63 with self.subTest(wrapped=True):
64 with self.subTest(desc="public"):
65 self.s.request("foo")
66 retry_call.assert_called_with(request,
67 delay=1, tries=10, fargs=["foo"],
68 fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
69 exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
70 request.assert_not_called()
71
72 with self.subTest(desc="private GET"):
73 self.s.request("foo", api="private")
74 retry_call.assert_called_with(request,
75 delay=1, tries=10, fargs=["foo"],
76 fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
77 exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
78 request.assert_not_called()
79
80 with self.subTest(desc="private POST regexp"):
81 self.s.request("returnFoo", api="private", method="POST")
82 retry_call.assert_called_with(request,
83 delay=1, tries=10, fargs=["returnFoo"],
84 fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
85 exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
86 request.assert_not_called()
87
88 with self.subTest(desc="private POST non-regexp"):
89 self.s.request("getMarginPosition", api="private", method="POST")
90 retry_call.assert_called_with(request,
91 delay=1, tries=10, fargs=["getMarginPosition"],
92 fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
93 exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
94 request.assert_not_called()
95 retry_call.reset_mock()
96 request.reset_mock()
97 with self.subTest(wrapped=False):
98 with self.subTest(desc="private POST non-matching regexp"):
99 self.s.request("marginBuy", api="private", method="POST")
100 request.assert_called_with("marginBuy",
101 api="private", method="POST", params={},
102 headers=None, body=None)
103 retry_call.assert_not_called()
104
105 with self.subTest(desc="private POST non-matching non-regexp"):
106 self.s.request("closeMarginPositionOther", api="private", method="POST")
107 request.assert_called_with("closeMarginPositionOther",
108 api="private", method="POST", params={},
109 headers=None, body=None)
110 retry_call.assert_not_called()
111
112 def test_order_precision(self):
113 self.assertEqual(8, self.s.order_precision("FOO"))
114
115 def test_transfer_balance(self):
116 with self.subTest(success=True),\
117 mock.patch.object(self.s, "privatePostTransferBalance") as t:
118 t.return_value = { "success": 1 }
119 result = self.s.transfer_balance("FOO", 12, "exchange", "margin")
120 t.assert_called_once_with({
121 "currency": "FOO",
122 "amount": 12,
123 "fromAccount": "exchange",
124 "toAccount": "margin",
125 "confirmed": 1
126 })
127 self.assertTrue(result)
128
129 with self.subTest(success=False),\
130 mock.patch.object(self.s, "privatePostTransferBalance") as t:
131 t.return_value = { "success": 0 }
132 self.assertFalse(self.s.transfer_balance("FOO", 12, "exchange", "margin"))
133
134 def test_close_margin_position(self):
135 with mock.patch.object(self.s, "privatePostCloseMarginPosition") as c:
136 self.s.close_margin_position("FOO", "BAR")
137 c.assert_called_with({"currencyPair": "BAR_FOO"})
138
139 def test_tradable_balances(self):
140 with mock.patch.object(self.s, "privatePostReturnTradableBalances") as r:
141 r.return_value = {
142 "FOO": { "exchange": "12.1234", "margin": "0.0123" },
143 "BAR": { "exchange": "1", "margin": "0" },
144 }
145 balances = self.s.tradable_balances()
146 self.assertEqual(["FOO", "BAR"], list(balances.keys()))
147 self.assertEqual(["exchange", "margin"], list(balances["FOO"].keys()))
148 self.assertEqual(D("12.1234"), balances["FOO"]["exchange"])
149 self.assertEqual(["exchange", "margin"], list(balances["BAR"].keys()))
150
151 def test_margin_summary(self):
152 with mock.patch.object(self.s, "privatePostReturnMarginAccountSummary") as r:
153 r.return_value = {
154 "currentMargin": "1.49680968",
155 "lendingFees": "0.0000001",
156 "pl": "0.00008254",
157 "totalBorrowedValue": "0.00673602",
158 "totalValue": "0.01000000",
159 "netValue": "0.01008254",
160 }
161 expected = {
162 'current_margin': D('1.49680968'),
163 'gains': D('0.00008254'),
164 'lending_fees': D('0.0000001'),
165 'total': D('0.01000000'),
166 'total_borrowed': D('0.00673602')
167 }
168 self.assertEqual(expected, self.s.margin_summary())
169
170 def test_create_order(self):
171 with mock.patch.object(self.s, "create_exchange_order") as exchange,\
172 mock.patch.object(self.s, "create_margin_order") as margin:
173 with self.subTest(account="unspecified"):
174 self.s.create_order("symbol", "type", "side", "amount", price="price", lending_rate="lending_rate", params="params")
175 exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
176 margin.assert_not_called()
177 exchange.reset_mock()
178 margin.reset_mock()
179
180 with self.subTest(account="exchange"):
181 self.s.create_order("symbol", "type", "side", "amount", account="exchange", price="price", lending_rate="lending_rate", params="params")
182 exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
183 margin.assert_not_called()
184 exchange.reset_mock()
185 margin.reset_mock()
186
187 with self.subTest(account="margin"):
188 self.s.create_order("symbol", "type", "side", "amount", account="margin", price="price", lending_rate="lending_rate", params="params")
189 margin.assert_called_once_with("symbol", "type", "side", "amount", lending_rate="lending_rate", price="price", params="params")
190 exchange.assert_not_called()
191 exchange.reset_mock()
192 margin.reset_mock()
193
194 with self.subTest(account="unknown"), self.assertRaises(NotImplementedError):
195 self.s.create_order("symbol", "type", "side", "amount", account="unknown")
196
197 def test_parse_ticker(self):
198 ticker = {
199 "high24hr": "12",
200 "low24hr": "10",
201 "highestBid": "10.5",
202 "lowestAsk": "11.5",
203 "last": "11",
204 "percentChange": "0.1",
205 "quoteVolume": "10",
206 "baseVolume": "20"
207 }
208 market = {
209 "symbol": "BTC/ETC"
210 }
211 with mock.patch.object(self.s, "milliseconds") as ms:
212 ms.return_value = 1520292715123
213 result = self.s.parse_ticker(ticker, market)
214
215 expected = {
216 "symbol": "BTC/ETC",
217 "timestamp": 1520292715123,
218 "datetime": "2018-03-05T23:31:55.123Z",
219 "high": D("12"),
220 "low": D("10"),
221 "bid": D("10.5"),
222 "ask": D("11.5"),
223 "vwap": None,
224 "open": None,
225 "close": None,
226 "first": None,
227 "last": D("11"),
228 "change": D("0.1"),
229 "percentage": None,
230 "average": None,
231 "baseVolume": D("10"),
232 "quoteVolume": D("20"),
233 "info": ticker
234 }
235 self.assertEqual(expected, result)
236
237 def test_fetch_margin_balance(self):
238 with mock.patch.object(self.s, "privatePostGetMarginPosition") as get_margin_position:
239 get_margin_position.return_value = {
240 "BTC_DASH": {
241 "amount": "-0.1",
242 "basePrice": "0.06818560",
243 "lendingFees": "0.00000001",
244 "liquidationPrice": "0.15107132",
245 "pl": "-0.00000371",
246 "total": "0.00681856",
247 "type": "short"
248 },
249 "BTC_ETC": {
250 "amount": "-0.6",
251 "basePrice": "0.1",
252 "lendingFees": "0.00000001",
253 "liquidationPrice": "0.6",
254 "pl": "0.00000371",
255 "total": "0.06",
256 "type": "short"
257 },
258 "BTC_ETH": {
259 "amount": "0",
260 "basePrice": "0",
261 "lendingFees": "0",
262 "liquidationPrice": "-1",
263 "pl": "0",
264 "total": "0",
265 "type": "none"
266 }
267 }
268 balances = self.s.fetch_margin_balance()
269 self.assertEqual(2, len(balances))
270 expected = {
271 "DASH": {
272 "amount": D("-0.1"),
273 "borrowedPrice": D("0.06818560"),
274 "lendingFees": D("1E-8"),
275 "pl": D("-0.00000371"),
276 "liquidationPrice": D("0.15107132"),
277 "type": "short",
278 "total": D("0.00681856"),
279 "baseCurrency": "BTC"
280 },
281 "ETC": {
282 "amount": D("-0.6"),
283 "borrowedPrice": D("0.1"),
284 "lendingFees": D("1E-8"),
285 "pl": D("0.00000371"),
286 "liquidationPrice": D("0.6"),
287 "type": "short",
288 "total": D("0.06"),
289 "baseCurrency": "BTC"
290 }
291 }
292 self.assertEqual(expected, balances)
293
294 def test_sum(self):
295 self.assertEqual(D("1.1"), self.s.sum(D("1"), D("0.1")))
296
297 def test_fetch_balance(self):
298 with mock.patch.object(self.s, "load_markets") as load_markets,\
299 mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balances,\
300 mock.patch.object(self.s, "common_currency_code") as ccc:
301 ccc.side_effect = ["ETH", "BTC", "DASH"]
302 balances.return_value = {
303 "ETH": {
304 "available": "10",
305 "onOrders": "1",
306 },
307 "BTC": {
308 "available": "1",
309 "onOrders": "0",
310 },
311 "DASH": {
312 "available": "0",
313 "onOrders": "3"
314 }
315 }
316
317 expected = {
318 "info": {
319 "ETH": {"available": "10", "onOrders": "1"},
320 "BTC": {"available": "1", "onOrders": "0"},
321 "DASH": {"available": "0", "onOrders": "3"}
322 },
323 "ETH": {"free": D("10"), "used": D("1"), "total": D("11")},
324 "BTC": {"free": D("1"), "used": D("0"), "total": D("1")},
325 "DASH": {"free": D("0"), "used": D("3"), "total": D("3")},
326 "free": {"ETH": D("10"), "BTC": D("1"), "DASH": D("0")},
327 "used": {"ETH": D("1"), "BTC": D("0"), "DASH": D("3")},
328 "total": {"ETH": D("11"), "BTC": D("1"), "DASH": D("3")}
329 }
330 result = self.s.fetch_balance()
331 load_markets.assert_called_once()
332 self.assertEqual(expected, result)
333
334 def test_fetch_balance_per_type(self):
335 with mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balances:
336 balances.return_value = {
337 "exchange": {
338 "BLK": "159.83673869",
339 "BTC": "0.00005959",
340 "USDT": "0.00002625",
341 "XMR": "0.18719303"
342 },
343 "margin": {
344 "BTC": "0.03019227"
345 }
346 }
347 expected = {
348 "info": {
349 "exchange": {
350 "BLK": "159.83673869",
351 "BTC": "0.00005959",
352 "USDT": "0.00002625",
353 "XMR": "0.18719303"
354 },
355 "margin": {
356 "BTC": "0.03019227"
357 }
358 },
359 "exchange": {
360 "BLK": D("159.83673869"),
361 "BTC": D("0.00005959"),
362 "USDT": D("0.00002625"),
363 "XMR": D("0.18719303")
364 },
365 "margin": {"BTC": D("0.03019227")},
366 "BLK": {"exchange": D("159.83673869")},
367 "BTC": {"exchange": D("0.00005959"), "margin": D("0.03019227")},
368 "USDT": {"exchange": D("0.00002625")},
369 "XMR": {"exchange": D("0.18719303")}
370 }
371 result = self.s.fetch_balance_per_type()
372 self.assertEqual(expected, result)
373
374 def test_fetch_all_balances(self):
375 import json
376 with mock.patch.object(self.s, "load_markets") as load_markets,\
377 mock.patch.object(self.s, "privatePostGetMarginPosition") as margin_balance,\
378 mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balance,\
379 mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balance_per_type:
380
381 with open("test_samples/poloniexETest.test_fetch_all_balances.1.json") as f:
382 balance.return_value = json.load(f)
383 with open("test_samples/poloniexETest.test_fetch_all_balances.2.json") as f:
384 margin_balance.return_value = json.load(f)
385 with open("test_samples/poloniexETest.test_fetch_all_balances.3.json") as f:
386 balance_per_type.return_value = json.load(f)
387
388 result = self.s.fetch_all_balances()
389 expected_doge = {
390 "total": D("-12779.79821852"),
391 "exchange_used": D("0E-8"),
392 "exchange_total": D("0E-8"),
393 "exchange_free": D("0E-8"),
394 "margin_available": 0,
395 "margin_in_position": 0,
396 "margin_borrowed": D("12779.79821852"),
397 "margin_total": D("-12779.79821852"),
398 "margin_pending_gain": 0,
399 "margin_lending_fees": D("-9E-8"),
400 "margin_pending_base_gain": D("0.00024059"),
401 "margin_position_type": "short",
402 "margin_liquidation_price": D("0.00000246"),
403 "margin_borrowed_base_price": D("0.00599149"),
404 "margin_borrowed_base_currency": "BTC"
405 }
406 expected_btc = {"total": D("0.05432165"),
407 "exchange_used": D("0E-8"),
408 "exchange_total": D("0.00005959"),
409 "exchange_free": D("0.00005959"),
410 "margin_available": D("0.03019227"),
411 "margin_in_position": D("0.02406979"),
412 "margin_borrowed": 0,
413 "margin_total": D("0.05426206"),
414 "margin_pending_gain": D("0.00093955"),
415 "margin_lending_fees": 0,
416 "margin_pending_base_gain": 0,
417 "margin_position_type": None,
418 "margin_liquidation_price": 0,
419 "margin_borrowed_base_price": 0,
420 "margin_borrowed_base_currency": None
421 }
422 expected_xmr = {"total": D("0.18719303"),
423 "exchange_used": D("0E-8"),
424 "exchange_total": D("0.18719303"),
425 "exchange_free": D("0.18719303"),
426 "margin_available": 0,
427 "margin_in_position": 0,
428 "margin_borrowed": 0,
429 "margin_total": 0,
430 "margin_pending_gain": 0,
431 "margin_lending_fees": 0,
432 "margin_pending_base_gain": 0,
433 "margin_position_type": None,
434 "margin_liquidation_price": 0,
435 "margin_borrowed_base_price": 0,
436 "margin_borrowed_base_currency": None
437 }
438 self.assertEqual(expected_xmr, result["XMR"])
439 self.assertEqual(expected_doge, result["DOGE"])
440 self.assertEqual(expected_btc, result["BTC"])
441
442 def test_create_margin_order(self):
443 with self.assertRaises(market.ExchangeError):
444 self.s.create_margin_order("FOO", "market", "buy", "10")
445
446 with mock.patch.object(self.s, "load_markets") as load_markets,\
447 mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
448 mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
449 mock.patch.object(self.s, "market") as market_mock,\
450 mock.patch.object(self.s, "price_to_precision") as ptp,\
451 mock.patch.object(self.s, "amount_to_precision") as atp:
452
453 margin_buy.return_value = {
454 "orderNumber": 123
455 }
456 margin_sell.return_value = {
457 "orderNumber": 456
458 }
459 market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
460 ptp.return_value = D("0.1")
461 atp.return_value = D("12")
462
463 order = self.s.create_margin_order("BTC_ETC", "margin", "buy", "12", price="0.1")
464 self.assertEqual(123, order["id"])
465 margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
466 margin_sell.assert_not_called()
467 margin_buy.reset_mock()
468 margin_sell.reset_mock()
469
470 order = self.s.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate="0.01", price="0.1")
471 self.assertEqual(456, order["id"])
472 margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
473 margin_buy.assert_not_called()
474
475 def test_create_exchange_order(self):
476 with mock.patch.object(market.ccxt.poloniex, "create_order") as create_order:
477 self.s.create_order("symbol", "type", "side", "amount", price="price", params="params")
478
479 create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
480
481 def test_common_currency_code(self):
482 self.assertEqual("FOO", self.s.common_currency_code("FOO"))
483
484 def test_currency_id(self):
485 self.assertEqual("FOO", self.s.currency_id("FOO"))