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