]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blame - tests/test_ccxt_wrapper.py
Merge branch 'dev'
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / tests / test_ccxt_wrapper.py
CommitLineData
3080f31d 1from .helper import limits, unittest, mock, D
c682bdf4
IB
2import requests_mock
3import market
4
3080f31d 5@unittest.skipUnless("unit" in limits, "Unit skipped")
c682bdf4
IB
6class 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()
e7d7c0e5
IB
25 ccxt._market.market_id = 3
26 ccxt._market.user_id = 3
c682bdf4
IB
27
28 ccxt.session.request("GET", "URL", data="data",
e7d7c0e5 29 headers={})
c682bdf4 30 ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
e7d7c0e5 31 {'X-market-id': '3', 'X-user-id': '3'}, 'response')
c682bdf4
IB
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()
e7d7c0e5
IB
40 ccxt._market.market_id = 3
41 ccxt._market.user_id = 3
c682bdf4
IB
42
43 with self.assertRaises(market.ccxt.RequestException, msg="Boo") as cm:
44 ccxt.session.request("GET", "URL", data="data",
e7d7c0e5 45 headers={})
c682bdf4 46 ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
e7d7c0e5 47 {'X-market-id': '3', 'X-user-id': '3'}, cm.exception)
c682bdf4
IB
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):
52ea19aa
IB
113 self.s.markets = {
114 "FOO": {
115 "precision": {
116 "price": 5,
117 "amount": 6,
118 }
119 },
120 "BAR": {
121 "precision": {
122 "price": 7,
123 "amount": 8,
124 }
125 }
126 }
127 with mock.patch.object(self.s, "load_markets") as load_markets:
128 self.assertEqual(5, self.s.order_precision("FOO"))
129 load_markets.assert_called_once()
c682bdf4
IB
130
131 def test_transfer_balance(self):
132 with self.subTest(success=True),\
133 mock.patch.object(self.s, "privatePostTransferBalance") as t:
134 t.return_value = { "success": 1 }
135 result = self.s.transfer_balance("FOO", 12, "exchange", "margin")
136 t.assert_called_once_with({
137 "currency": "FOO",
138 "amount": 12,
139 "fromAccount": "exchange",
140 "toAccount": "margin",
141 "confirmed": 1
142 })
143 self.assertTrue(result)
144
145 with self.subTest(success=False),\
146 mock.patch.object(self.s, "privatePostTransferBalance") as t:
147 t.return_value = { "success": 0 }
148 self.assertFalse(self.s.transfer_balance("FOO", 12, "exchange", "margin"))
149
150 def test_close_margin_position(self):
151 with mock.patch.object(self.s, "privatePostCloseMarginPosition") as c:
152 self.s.close_margin_position("FOO", "BAR")
153 c.assert_called_with({"currencyPair": "BAR_FOO"})
154
155 def test_tradable_balances(self):
156 with mock.patch.object(self.s, "privatePostReturnTradableBalances") as r:
157 r.return_value = {
158 "FOO": { "exchange": "12.1234", "margin": "0.0123" },
159 "BAR": { "exchange": "1", "margin": "0" },
160 }
161 balances = self.s.tradable_balances()
162 self.assertEqual(["FOO", "BAR"], list(balances.keys()))
163 self.assertEqual(["exchange", "margin"], list(balances["FOO"].keys()))
164 self.assertEqual(D("12.1234"), balances["FOO"]["exchange"])
165 self.assertEqual(["exchange", "margin"], list(balances["BAR"].keys()))
166
167 def test_margin_summary(self):
168 with mock.patch.object(self.s, "privatePostReturnMarginAccountSummary") as r:
169 r.return_value = {
170 "currentMargin": "1.49680968",
171 "lendingFees": "0.0000001",
172 "pl": "0.00008254",
173 "totalBorrowedValue": "0.00673602",
174 "totalValue": "0.01000000",
175 "netValue": "0.01008254",
176 }
177 expected = {
178 'current_margin': D('1.49680968'),
179 'gains': D('0.00008254'),
180 'lending_fees': D('0.0000001'),
181 'total': D('0.01000000'),
182 'total_borrowed': D('0.00673602')
183 }
184 self.assertEqual(expected, self.s.margin_summary())
185
c682bdf4
IB
186 def test_parse_ticker(self):
187 ticker = {
188 "high24hr": "12",
189 "low24hr": "10",
190 "highestBid": "10.5",
191 "lowestAsk": "11.5",
192 "last": "11",
193 "percentChange": "0.1",
194 "quoteVolume": "10",
195 "baseVolume": "20"
196 }
197 market = {
198 "symbol": "BTC/ETC"
199 }
200 with mock.patch.object(self.s, "milliseconds") as ms:
201 ms.return_value = 1520292715123
202 result = self.s.parse_ticker(ticker, market)
203
204 expected = {
205 "symbol": "BTC/ETC",
206 "timestamp": 1520292715123,
207 "datetime": "2018-03-05T23:31:55.123Z",
208 "high": D("12"),
209 "low": D("10"),
210 "bid": D("10.5"),
211 "ask": D("11.5"),
212 "vwap": None,
213 "open": None,
214 "close": None,
215 "first": None,
216 "last": D("11"),
217 "change": D("0.1"),
218 "percentage": None,
219 "average": None,
220 "baseVolume": D("10"),
221 "quoteVolume": D("20"),
222 "info": ticker
223 }
224 self.assertEqual(expected, result)
225
226 def test_fetch_margin_balance(self):
227 with mock.patch.object(self.s, "privatePostGetMarginPosition") as get_margin_position:
228 get_margin_position.return_value = {
229 "BTC_DASH": {
230 "amount": "-0.1",
231 "basePrice": "0.06818560",
232 "lendingFees": "0.00000001",
233 "liquidationPrice": "0.15107132",
234 "pl": "-0.00000371",
235 "total": "0.00681856",
236 "type": "short"
237 },
238 "BTC_ETC": {
239 "amount": "-0.6",
240 "basePrice": "0.1",
241 "lendingFees": "0.00000001",
242 "liquidationPrice": "0.6",
243 "pl": "0.00000371",
244 "total": "0.06",
245 "type": "short"
246 },
247 "BTC_ETH": {
248 "amount": "0",
249 "basePrice": "0",
250 "lendingFees": "0",
251 "liquidationPrice": "-1",
252 "pl": "0",
253 "total": "0",
254 "type": "none"
255 }
256 }
257 balances = self.s.fetch_margin_balance()
258 self.assertEqual(2, len(balances))
259 expected = {
260 "DASH": {
261 "amount": D("-0.1"),
262 "borrowedPrice": D("0.06818560"),
263 "lendingFees": D("1E-8"),
264 "pl": D("-0.00000371"),
265 "liquidationPrice": D("0.15107132"),
266 "type": "short",
267 "total": D("0.00681856"),
268 "baseCurrency": "BTC"
269 },
270 "ETC": {
271 "amount": D("-0.6"),
272 "borrowedPrice": D("0.1"),
273 "lendingFees": D("1E-8"),
274 "pl": D("0.00000371"),
275 "liquidationPrice": D("0.6"),
276 "type": "short",
277 "total": D("0.06"),
278 "baseCurrency": "BTC"
279 }
280 }
281 self.assertEqual(expected, balances)
282
283 def test_sum(self):
284 self.assertEqual(D("1.1"), self.s.sum(D("1"), D("0.1")))
285
286 def test_fetch_balance(self):
287 with mock.patch.object(self.s, "load_markets") as load_markets,\
288 mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balances,\
289 mock.patch.object(self.s, "common_currency_code") as ccc:
290 ccc.side_effect = ["ETH", "BTC", "DASH"]
291 balances.return_value = {
292 "ETH": {
293 "available": "10",
294 "onOrders": "1",
295 },
296 "BTC": {
297 "available": "1",
298 "onOrders": "0",
299 },
300 "DASH": {
301 "available": "0",
302 "onOrders": "3"
303 }
304 }
305
306 expected = {
307 "info": {
308 "ETH": {"available": "10", "onOrders": "1"},
309 "BTC": {"available": "1", "onOrders": "0"},
310 "DASH": {"available": "0", "onOrders": "3"}
311 },
312 "ETH": {"free": D("10"), "used": D("1"), "total": D("11")},
313 "BTC": {"free": D("1"), "used": D("0"), "total": D("1")},
314 "DASH": {"free": D("0"), "used": D("3"), "total": D("3")},
315 "free": {"ETH": D("10"), "BTC": D("1"), "DASH": D("0")},
316 "used": {"ETH": D("1"), "BTC": D("0"), "DASH": D("3")},
317 "total": {"ETH": D("11"), "BTC": D("1"), "DASH": D("3")}
318 }
319 result = self.s.fetch_balance()
320 load_markets.assert_called_once()
321 self.assertEqual(expected, result)
322
323 def test_fetch_balance_per_type(self):
324 with mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balances:
325 balances.return_value = {
326 "exchange": {
327 "BLK": "159.83673869",
328 "BTC": "0.00005959",
329 "USDT": "0.00002625",
330 "XMR": "0.18719303"
331 },
332 "margin": {
333 "BTC": "0.03019227"
334 }
335 }
336 expected = {
337 "info": {
338 "exchange": {
339 "BLK": "159.83673869",
340 "BTC": "0.00005959",
341 "USDT": "0.00002625",
342 "XMR": "0.18719303"
343 },
344 "margin": {
345 "BTC": "0.03019227"
346 }
347 },
348 "exchange": {
349 "BLK": D("159.83673869"),
350 "BTC": D("0.00005959"),
351 "USDT": D("0.00002625"),
352 "XMR": D("0.18719303")
353 },
354 "margin": {"BTC": D("0.03019227")},
355 "BLK": {"exchange": D("159.83673869")},
356 "BTC": {"exchange": D("0.00005959"), "margin": D("0.03019227")},
357 "USDT": {"exchange": D("0.00002625")},
358 "XMR": {"exchange": D("0.18719303")}
359 }
360 result = self.s.fetch_balance_per_type()
361 self.assertEqual(expected, result)
362
363 def test_fetch_all_balances(self):
364 import json
365 with mock.patch.object(self.s, "load_markets") as load_markets,\
366 mock.patch.object(self.s, "privatePostGetMarginPosition") as margin_balance,\
367 mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balance,\
368 mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balance_per_type:
369
370 with open("test_samples/poloniexETest.test_fetch_all_balances.1.json") as f:
371 balance.return_value = json.load(f)
372 with open("test_samples/poloniexETest.test_fetch_all_balances.2.json") as f:
373 margin_balance.return_value = json.load(f)
374 with open("test_samples/poloniexETest.test_fetch_all_balances.3.json") as f:
375 balance_per_type.return_value = json.load(f)
376
377 result = self.s.fetch_all_balances()
378 expected_doge = {
379 "total": D("-12779.79821852"),
380 "exchange_used": D("0E-8"),
381 "exchange_total": D("0E-8"),
382 "exchange_free": D("0E-8"),
383 "margin_available": 0,
384 "margin_in_position": 0,
385 "margin_borrowed": D("12779.79821852"),
386 "margin_total": D("-12779.79821852"),
387 "margin_pending_gain": 0,
388 "margin_lending_fees": D("-9E-8"),
389 "margin_pending_base_gain": D("0.00024059"),
390 "margin_position_type": "short",
391 "margin_liquidation_price": D("0.00000246"),
392 "margin_borrowed_base_price": D("0.00599149"),
393 "margin_borrowed_base_currency": "BTC"
394 }
395 expected_btc = {"total": D("0.05432165"),
396 "exchange_used": D("0E-8"),
397 "exchange_total": D("0.00005959"),
398 "exchange_free": D("0.00005959"),
399 "margin_available": D("0.03019227"),
400 "margin_in_position": D("0.02406979"),
401 "margin_borrowed": 0,
402 "margin_total": D("0.05426206"),
403 "margin_pending_gain": D("0.00093955"),
404 "margin_lending_fees": 0,
405 "margin_pending_base_gain": 0,
406 "margin_position_type": None,
407 "margin_liquidation_price": 0,
408 "margin_borrowed_base_price": 0,
409 "margin_borrowed_base_currency": None
410 }
411 expected_xmr = {"total": D("0.18719303"),
412 "exchange_used": D("0E-8"),
413 "exchange_total": D("0.18719303"),
414 "exchange_free": D("0.18719303"),
415 "margin_available": 0,
416 "margin_in_position": 0,
417 "margin_borrowed": 0,
418 "margin_total": 0,
419 "margin_pending_gain": 0,
420 "margin_lending_fees": 0,
421 "margin_pending_base_gain": 0,
422 "margin_position_type": None,
423 "margin_liquidation_price": 0,
424 "margin_borrowed_base_price": 0,
425 "margin_borrowed_base_currency": None
426 }
427 self.assertEqual(expected_xmr, result["XMR"])
428 self.assertEqual(expected_doge, result["DOGE"])
429 self.assertEqual(expected_btc, result["BTC"])
430
52ea19aa
IB
431 def test_create_order(self):
432 with self.subTest(type="market"),\
433 self.assertRaises(market.ExchangeError):
434 self.s.create_order("FOO", "market", "buy", "10")
c682bdf4 435
52ea19aa
IB
436 with self.subTest(type="limit", account="margin"),\
437 mock.patch.object(self.s, "load_markets") as load_markets,\
c682bdf4
IB
438 mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
439 mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
440 mock.patch.object(self.s, "market") as market_mock,\
441 mock.patch.object(self.s, "price_to_precision") as ptp,\
442 mock.patch.object(self.s, "amount_to_precision") as atp:
443
444 margin_buy.return_value = {
445 "orderNumber": 123
446 }
447 margin_sell.return_value = {
448 "orderNumber": 456
449 }
450 market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
451 ptp.return_value = D("0.1")
452 atp.return_value = D("12")
453
52ea19aa
IB
454 order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
455 account="margin", price="0.1")
c682bdf4
IB
456 self.assertEqual(123, order["id"])
457 margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
458 margin_sell.assert_not_called()
459 margin_buy.reset_mock()
460 margin_sell.reset_mock()
461
52ea19aa
IB
462 order = self.s.create_order("BTC_ETC", "limit", "sell",
463 "12", account="margin", lending_rate="0.01", price="0.1")
c682bdf4
IB
464 self.assertEqual(456, order["id"])
465 margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
466 margin_buy.assert_not_called()
467
52ea19aa
IB
468 with self.subTest(type="limit", account="exchange"),\
469 mock.patch.object(self.s, "load_markets") as load_markets,\
470 mock.patch.object(self.s, "privatePostBuy") as exchange_buy,\
471 mock.patch.object(self.s, "privatePostSell") as exchange_sell,\
472 mock.patch.object(self.s, "market") as market_mock,\
473 mock.patch.object(self.s, "price_to_precision") as ptp,\
474 mock.patch.object(self.s, "amount_to_precision") as atp:
c682bdf4 475
52ea19aa
IB
476 exchange_buy.return_value = {
477 "orderNumber": 123
478 }
479 exchange_sell.return_value = {
480 "orderNumber": 456
481 }
482 market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
483 ptp.return_value = D("0.1")
484 atp.return_value = D("12")
485
486 order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
487 account="exchange", price="0.1")
488 self.assertEqual(123, order["id"])
489 exchange_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
490 exchange_sell.assert_not_called()
491 exchange_buy.reset_mock()
492 exchange_sell.reset_mock()
493
494 order = self.s.create_order("BTC_ETC", "limit", "sell",
495 "12", account="exchange", lending_rate="0.01", price="0.1")
496 self.assertEqual(456, order["id"])
497 exchange_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
498 exchange_buy.assert_not_called()
499
500 with self.subTest(account="unknown"), self.assertRaises(NotImplementedError),\
501 mock.patch.object(self.s, "load_markets") as load_markets:
502 self.s.create_order("symbol", "type", "side", "amount", account="unknown")
c682bdf4 503
18de421e
IB
504 def test_common_currency_code(self):
505 self.assertEqual("FOO", self.s.common_currency_code("FOO"))
c682bdf4 506
18de421e
IB
507 def test_currency_id(self):
508 self.assertEqual("FOO", self.s.currency_id("FOO"))
52ea19aa
IB
509
510 def test_amount_to_precision(self):
511 self.s.markets = {
512 "FOO": {
513 "precision": {
514 "price": 5,
515 "amount": 6,
516 }
517 },
518 "BAR": {
519 "precision": {
520 "price": 7,
521 "amount": 8,
522 }
523 }
524 }
525 self.assertEqual("0.0001", self.s.amount_to_precision("FOO", D("0.0001")))
526 self.assertEqual("0.0000001", self.s.amount_to_precision("BAR", D("0.0000001")))
527 self.assertEqual("0.000001", self.s.amount_to_precision("FOO", D("0.000001")))
528
529 def test_price_to_precision(self):
530 self.s.markets = {
531 "FOO": {
532 "precision": {
533 "price": 5,
534 "amount": 6,
535 }
536 },
537 "BAR": {
538 "precision": {
539 "price": 7,
540 "amount": 8,
541 }
542 }
543 }
544 self.assertEqual("0.0001", self.s.price_to_precision("FOO", D("0.0001")))
545 self.assertEqual("0.0000001", self.s.price_to_precision("BAR", D("0.0000001")))
546 self.assertEqual("0", self.s.price_to_precision("FOO", D("0.000001")))
547
1902674c
IB
548 def test_is_dust_trade(self):
549 self.assertTrue(self.s.is_dust_trade(D("0.0000009"), D("1000")))
550 self.assertTrue(self.s.is_dust_trade(D("0.000001"), D("10")))
551 self.assertFalse(self.s.is_dust_trade(D("0.000001"), D("100")))
51bc7cde
IB
552
553 def test_fetch_nth_order_book(self):
554 with mock.patch.object(self.s, "fetch_order_book") as t:
555 t.return_value = {
556 "asks": [
557 [1.269e-05, 781.23105917],
558 [1.271e-05, 108.83577689],
559 [1.276e-05, 19162.15732141],
560 [1.277e-05, 34.13657561],
561 [1.28e-05, 95.14285714],
562 [1.281e-05, 11.13909862],
563 [1.282e-05, 43.42379871],
564 [1.284e-05, 493.67767887],
565 [1.288e-05, 6179.57843281],
566 [1.289e-05, 235.16250589]
567 ],
568 "bids": [
569 [1.266e-05, 3496.42283539],
570 [1.23e-05, 9.02439024],
571 [1.229e-05, 3244.25987796],
572 [1.228e-05, 6692.16061185],
573 [1.207e-05, 9.19635459],
574 [1.206e-05, 4711.05943978],
575 [1.194e-05, 84.67400508],
576 [1.168e-05, 61.75268779],
577 [1.165e-05, 9.52789699],
578 [1.157e-05, 16.4900605]
579 ]
580 }
581 self.assertAlmostEqual(D("0.00001289"), self.s.fetch_nth_order_book("BTC/HUC", "ask", 10), 8)
582 t.assert_called_once_with("BTC/HUC", limit=10)
583 self.assertAlmostEqual(D("0.00001157"), self.s.fetch_nth_order_book("BTC/HUC", "bid", 10), 8)