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