]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Move ticker to Trade class and add tests
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
1 import portfolio
2 import unittest
3 from unittest import mock
4
5 class AmountTest(unittest.TestCase):
6 def test_values(self):
7 amount = portfolio.Amount("BTC", 0.65)
8 self.assertEqual(0.65, amount.value)
9 self.assertEqual("BTC", amount.currency)
10
11 amount = portfolio.Amount("BTC", 10, int_val=2000000000000000)
12 self.assertEqual(0.002, amount.value)
13
14 def test_in_currency(self):
15 amount = portfolio.Amount("ETC", 10)
16
17 self.assertEqual(amount, amount.in_currency("ETC", None))
18
19 ticker_mock = unittest.mock.Mock()
20 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
21 ticker_mock.return_value = None
22
23 self.assertRaises(Exception, amount.in_currency, "ETH", None)
24
25 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
26 ticker_mock.return_value = {
27 "average": 0.3,
28 "foo": "bar",
29 }
30 converted_amount = amount.in_currency("ETH", None)
31
32 self.assertEqual(3.0, converted_amount.value)
33 self.assertEqual("ETH", converted_amount.currency)
34 self.assertEqual(amount, converted_amount.linked_to)
35 self.assertEqual("bar", converted_amount.ticker["foo"])
36
37 def test__abs(self):
38 amount = portfolio.Amount("SC", -120)
39 self.assertEqual(120, abs(amount).value)
40 self.assertEqual("SC", abs(amount).currency)
41
42 amount = portfolio.Amount("SC", 10)
43 self.assertEqual(10, abs(amount).value)
44 self.assertEqual("SC", abs(amount).currency)
45
46 def test__add(self):
47 amount1 = portfolio.Amount("XVG", 12.9)
48 amount2 = portfolio.Amount("XVG", 13.1)
49
50 self.assertEqual(26, (amount1 + amount2).value)
51 self.assertEqual("XVG", (amount1 + amount2).currency)
52
53 amount3 = portfolio.Amount("ETH", 1.6)
54 with self.assertRaises(Exception):
55 amount1 + amount3
56
57 amount4 = portfolio.Amount("ETH", 0.0)
58 self.assertEqual(amount1, amount1 + amount4)
59
60 def test__radd(self):
61 amount = portfolio.Amount("XVG", 12.9)
62
63 self.assertEqual(amount, 0 + amount)
64 with self.assertRaises(Exception):
65 4 + amount
66
67 def test__sub(self):
68 amount1 = portfolio.Amount("XVG", 13.3)
69 amount2 = portfolio.Amount("XVG", 13.1)
70
71 self.assertEqual(0.2, (amount1 - amount2).value)
72 self.assertEqual("XVG", (amount1 - amount2).currency)
73
74 amount3 = portfolio.Amount("ETH", 1.6)
75 with self.assertRaises(Exception):
76 amount1 - amount3
77
78 amount4 = portfolio.Amount("ETH", 0.0)
79 self.assertEqual(amount1, amount1 - amount4)
80
81 def test__int(self):
82 amount = portfolio.Amount("XMR", 0.1)
83 self.assertEqual(100000000000000000, int(amount))
84
85 def test__mul(self):
86 amount = portfolio.Amount("XEM", 11)
87
88 self.assertEqual(38.5, (amount * 3.5).value)
89 self.assertEqual(33, (amount * 3).value)
90
91 with self.assertRaises(Exception):
92 amount * amount
93
94 def test__rmul(self):
95 amount = portfolio.Amount("XEM", 11)
96
97 self.assertEqual(38.5, (3.5 * amount).value)
98 self.assertEqual(33, (3 * amount).value)
99
100 def test__floordiv(self):
101 amount = portfolio.Amount("XEM", 11)
102
103 self.assertEqual(5.5, (amount // 2).value)
104 with self.assertRaises(TypeError):
105 amount // 2.5
106 self.assertEqual(1571428571428571428, (amount // 7)._value)
107
108 def test__div(self):
109 amount = portfolio.Amount("XEM", 11)
110
111 with self.assertRaises(TypeError):
112 amount / 2.5
113 self.assertEqual(5.5, (amount / 2).value)
114 self.assertEqual(1571428571428571428, (amount / 7)._value)
115
116 def test__lt(self):
117 amount1 = portfolio.Amount("BTD", 11.3)
118 amount2 = portfolio.Amount("BTD", 13.1)
119
120 self.assertTrue(amount1 < amount2)
121 self.assertFalse(amount2 < amount1)
122 self.assertFalse(amount1 < amount1)
123
124 amount3 = portfolio.Amount("BTC", 1.6)
125 with self.assertRaises(Exception):
126 amount1 < amount3
127
128 def test__eq(self):
129 amount1 = portfolio.Amount("BTD", 11.3)
130 amount2 = portfolio.Amount("BTD", 13.1)
131 amount3 = portfolio.Amount("BTD", 11.3)
132
133 self.assertFalse(amount1 == amount2)
134 self.assertFalse(amount2 == amount1)
135 self.assertTrue(amount1 == amount3)
136 self.assertFalse(amount2 == 0)
137
138 amount4 = portfolio.Amount("BTC", 1.6)
139 with self.assertRaises(Exception):
140 amount1 == amount4
141
142 amount5 = portfolio.Amount("BTD", 0)
143 self.assertTrue(amount5 == 0)
144
145 def test__str(self):
146 amount1 = portfolio.Amount("BTX", 32)
147 self.assertEqual("32.00000000 BTX", str(amount1))
148
149 amount2 = portfolio.Amount("USDT", 12000)
150 amount1.linked_to = amount2
151 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
152
153 def test__repr(self):
154 amount1 = portfolio.Amount("BTX", 32)
155 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
156
157 amount2 = portfolio.Amount("USDT", 12000)
158 amount1.linked_to = amount2
159 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
160
161 amount3 = portfolio.Amount("BTC", 0.1)
162 amount2.linked_to = amount3
163 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
164
165 class PortfolioTest(unittest.TestCase):
166 import urllib3
167 def fill_data(self):
168 if self.json_response is not None:
169 portfolio.Portfolio.data = self.json_response
170
171 def setUp(self):
172 super(PortfolioTest, self).setUp()
173
174 with open("test_portfolio.json") as example:
175 import json
176 self.json_response = json.load(example)
177
178 self.patcher = mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={})
179 self.patcher.start()
180
181 @mock.patch.object(urllib3, "disable_warnings")
182 @mock.patch.object(urllib3.poolmanager.PoolManager, "request")
183 @mock.patch.object(portfolio.Portfolio, "URL", new="foo://bar")
184 def test_get_cryptoportfolio(self, request, disable_warnings):
185 request.side_effect = [
186 type('', (), { "data": '{ "foo": "bar" }' }),
187 type('', (), { "data": 'System Error' }),
188 Exception("Connection error"),
189 ]
190
191 portfolio.Portfolio.get_cryptoportfolio()
192 self.assertIn("foo", portfolio.Portfolio.data)
193 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
194 request.assert_called_with("GET", "foo://bar")
195
196 request.reset_mock()
197 portfolio.Portfolio.get_cryptoportfolio()
198 self.assertIsNone(portfolio.Portfolio.data)
199 request.assert_called_with("GET", "foo://bar")
200
201 request.reset_mock()
202 portfolio.Portfolio.data = "foo"
203 portfolio.Portfolio.get_cryptoportfolio()
204 request.assert_called_with("GET", "foo://bar")
205 self.assertEqual("foo", portfolio.Portfolio.data)
206 disable_warnings.assert_called_with()
207
208 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
209 def test_parse_cryptoportfolio(self, mock_get):
210 mock_get.side_effect = self.fill_data
211
212 portfolio.Portfolio.parse_cryptoportfolio()
213
214 self.assertListEqual(
215 ["medium", "high"],
216 list(portfolio.Portfolio.liquidities.keys()))
217
218 liquidities = portfolio.Portfolio.liquidities
219 self.assertEqual(10, len(liquidities["medium"].keys()))
220 self.assertEqual(10, len(liquidities["high"].keys()))
221
222 expected = {'BTC': 2857, 'DGB': 1015, 'DOGE': 1805, 'SC': 623, 'ZEC': 3701}
223 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
224
225 expected = {'ETC': 1000, 'FCT': 1000, 'GAS': 1000, 'NAV': 1000, 'OMG': 1000, 'OMNI': 1000, 'PPC': 1000, 'RIC': 1000, 'VIA': 1000, 'XCP': 1000}
226 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
227
228 # It doesn't refetch the data when available
229 portfolio.Portfolio.parse_cryptoportfolio()
230 mock_get.assert_called_once_with()
231
232 portfolio.Portfolio.data["portfolio_1"]["holding"]["direction"][3] = "short"
233 self.assertRaises(AssertionError, portfolio.Portfolio.parse_cryptoportfolio)
234
235 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
236 def test_repartition_pertenthousand(self, mock_get):
237 mock_get.side_effect = self.fill_data
238
239 expected_medium = {'USDT': 1000, 'ETC': 1000, 'FCT': 1000, 'OMG': 1000, 'STEEM': 1000, 'STRAT': 1000, 'XEM': 1000, 'XMR': 1000, 'XVC': 1000, 'ZRX': 1000}
240 expected_high = {'USDT': 1226, 'BTC': 1429, 'ETC': 1127, 'ETH': 1569, 'FCT': 3341, 'GAS': 1308}
241
242 self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand())
243 self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand(liquidity="medium"))
244 self.assertEqual(expected_high, portfolio.Portfolio.repartition_pertenthousand(liquidity="high"))
245
246 def tearDown(self):
247 self.patcher.stop()
248
249 class BalanceTest(unittest.TestCase):
250 def setUp(self):
251 super(BalanceTest, self).setUp()
252
253 self.fetch_balance = {
254 "free": "foo",
255 "info": "bar",
256 "used": "baz",
257 "total": "bazz",
258 "USDT": {
259 "free": 6.0,
260 "used": 1.2,
261 "total": 7.2
262 },
263 "XVG": {
264 "free": 16,
265 "used": 0.0,
266 "total": 16
267 },
268 "XMR": {
269 "free": 0.0,
270 "used": 0.0,
271 "total": 0.0
272 },
273 }
274 self.patcher = mock.patch.multiple(portfolio.Balance, known_balances={})
275 self.patcher.start()
276
277 def test_values(self):
278 balance = portfolio.Balance("BTC", 0.65, 0.35, 0.30)
279 self.assertEqual(0.65, balance.total.value)
280 self.assertEqual(0.35, balance.free.value)
281 self.assertEqual(0.30, balance.used.value)
282 self.assertEqual("BTC", balance.currency)
283
284 balance = portfolio.Balance.from_hash("BTC", { "total": 0.65, "free": 0.35, "used": 0.30})
285 self.assertEqual(0.65, balance.total.value)
286 self.assertEqual(0.35, balance.free.value)
287 self.assertEqual(0.30, balance.used.value)
288 self.assertEqual("BTC", balance.currency)
289
290 @mock.patch.object(portfolio.Trade, "get_ticker")
291 def test_in_currency(self, get_ticker):
292 portfolio.Balance.known_balances = {
293 "BTC": portfolio.Balance("BTC", 0.65, 0.35, 0.30),
294 "ETH": portfolio.Balance("ETH", 3, 3, 0),
295 }
296 market = mock.Mock()
297 get_ticker.return_value = {
298 "bid": 0.09,
299 "ask": 0.11,
300 "average": 0.1,
301 }
302
303 amounts = portfolio.Balance.in_currency("BTC", market)
304 self.assertEqual("BTC", amounts["ETH"].currency)
305 self.assertEqual(0.65, amounts["BTC"].value)
306 self.assertEqual(0.30, amounts["ETH"].value)
307
308 amounts = portfolio.Balance.in_currency("BTC", market, action="bid")
309 self.assertEqual(0.65, amounts["BTC"].value)
310 self.assertEqual(0.27, amounts["ETH"].value)
311
312 amounts = portfolio.Balance.in_currency("BTC", market, action="bid", type="used")
313 self.assertEqual(0.30, amounts["BTC"].value)
314 self.assertEqual(0, amounts["ETH"].value)
315
316 def test_currencies(self):
317 portfolio.Balance.known_balances = {
318 "BTC": portfolio.Balance("BTC", 0.65, 0.35, 0.30),
319 "ETH": portfolio.Balance("ETH", 3, 3, 0),
320 }
321 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
322
323 @mock.patch.object(portfolio.market, "fetch_balance")
324 def test_fetch_balances(self, fetch_balance):
325 fetch_balance.return_value = self.fetch_balance
326
327 portfolio.Balance.fetch_balances(portfolio.market)
328 self.assertNotIn("XMR", portfolio.Balance.currencies())
329 self.assertEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
330
331 @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
332 @mock.patch.object(portfolio.market, "fetch_balance")
333 def test_dispatch_assets(self, fetch_balance, repartition):
334 fetch_balance.return_value = self.fetch_balance
335 portfolio.Balance.fetch_balances(portfolio.market)
336
337 self.assertNotIn("XEM", portfolio.Balance.currencies())
338
339 repartition.return_value = {
340 "XEM": 7500,
341 "BTC": 2600,
342 }
343
344 amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", 10.1))
345 self.assertIn("XEM", portfolio.Balance.currencies())
346 self.assertEqual(2.6, amounts["BTC"].value)
347 self.assertEqual(7.5, amounts["XEM"].value)
348
349 @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
350 @mock.patch.object(portfolio.Trade, "get_ticker")
351 @mock.patch.object(portfolio.Trade, "compute_trades")
352 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
353 repartition.return_value = {
354 "XEM": 7500,
355 "BTC": 2500,
356 }
357 get_ticker.side_effect = [
358 { "average": 0.0001 },
359 { "average": 0.000001 }
360 ]
361 market = mock.Mock()
362 market.fetch_balance.return_value = {
363 "USDT": {
364 "free": 10000.0,
365 "used": 0.0,
366 "total": 10000.0
367 },
368 "XVG": {
369 "free": 10000.0,
370 "used": 0.0,
371 "total": 10000.0
372 },
373 }
374 portfolio.Balance.prepare_trades(market)
375 compute_trades.assert_called()
376
377 call = compute_trades.call_args
378 self.assertEqual(market, call[1]["market"])
379 self.assertEqual(1, call[0][0]["USDT"].value)
380 self.assertEqual(0.01, call[0][0]["XVG"].value)
381 self.assertEqual(0.2525, call[0][1]["BTC"].value)
382 self.assertEqual(0.7575, call[0][1]["XEM"].value)
383
384 def test__repr(self):
385 balance = portfolio.Balance("BTX", 3, 1, 2)
386 self.assertEqual("Balance(BTX [1.00000000 BTX/2.00000000 BTX/3.00000000 BTX])", repr(balance))
387
388 def tearDown(self):
389 self.patcher.stop()
390
391 class TradeTest(unittest.TestCase):
392 import time
393
394 def setUp(self):
395 super(TradeTest, self).setUp()
396
397 self.patcher = mock.patch.multiple(portfolio.Trade,
398 ticker_cache={},
399 ticker_cache_timestamp=self.time.time(),
400 fees_cache={},
401 trades={})
402 self.patcher.start()
403
404 def test_get_ticker(self):
405 market = mock.Mock()
406 market.fetch_ticker.side_effect = [
407 { "bid": 1, "ask": 3 },
408 portfolio.ccxt.ExchangeError("foo"),
409 { "bid": 10, "ask": 40 },
410 portfolio.ccxt.ExchangeError("foo"),
411 portfolio.ccxt.ExchangeError("foo"),
412 ]
413
414 ticker = portfolio.Trade.get_ticker("ETH", "ETC", market)
415 market.fetch_ticker.assert_called_with("ETH/ETC")
416 self.assertEqual(1, ticker["bid"])
417 self.assertEqual(3, ticker["ask"])
418 self.assertEqual(2, ticker["average"])
419 self.assertFalse(ticker["inverted"])
420
421 ticker = portfolio.Trade.get_ticker("ETH", "XVG", market)
422 self.assertEqual(0.0625, ticker["average"])
423 self.assertTrue(ticker["inverted"])
424 self.assertIn("original", ticker)
425 self.assertEqual(10, ticker["original"]["bid"])
426
427 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market)
428 self.assertIsNone(ticker)
429
430 market.fetch_ticker.assert_has_calls([
431 mock.call("ETH/ETC"),
432 mock.call("ETH/XVG"),
433 mock.call("XVG/ETH"),
434 mock.call("XVG/XMR"),
435 mock.call("XMR/XVG"),
436 ])
437
438 market2 = mock.Mock()
439 market2.fetch_ticker.side_effect = [
440 { "bid": 1, "ask": 3 },
441 { "bid": 1.2, "ask": 3.5 },
442 ]
443 ticker1 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
444 ticker2 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
445 ticker3 = portfolio.Trade.get_ticker("ETC", "ETH", market2)
446 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
447 self.assertEqual(1, ticker1["bid"])
448 self.assertDictEqual(ticker1, ticker2)
449 self.assertDictEqual(ticker1, ticker3["original"])
450
451 ticker4 = portfolio.Trade.get_ticker("ETH", "ETC", market2, refresh=True)
452 ticker5 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
453 self.assertEqual(1.2, ticker4["bid"])
454 self.assertDictEqual(ticker4, ticker5)
455
456 market3 = mock.Mock()
457 market3.fetch_ticker.side_effect = [
458 { "bid": 1, "ask": 3 },
459 { "bid": 1.2, "ask": 3.5 },
460 ]
461 ticker6 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
462 portfolio.Trade.ticker_cache_timestamp -= 4
463 ticker7 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
464 portfolio.Trade.ticker_cache_timestamp -= 2
465 ticker8 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
466 self.assertDictEqual(ticker6, ticker7)
467 self.assertEqual(1.2, ticker8["bid"])
468
469 @unittest.skip("TODO")
470 def test_values_assertion(self):
471 pass
472
473 @unittest.skip("TODO")
474 def test_fetch_fees(self):
475 pass
476
477 @unittest.skip("TODO")
478 def test_compute_trades(self):
479 pass
480
481 @unittest.skip("TODO")
482 def test_action(self):
483 pass
484
485 @unittest.skip("TODO")
486 def test_action(self):
487 pass
488
489 @unittest.skip("TODO")
490 def test_order_action(self):
491 pass
492
493 @unittest.skip("TODO")
494 def test_prepare_order(self):
495 pass
496
497 @unittest.skip("TODO")
498 def test_all_orders(self):
499 pass
500
501 @unittest.skip("TODO")
502 def test_follow_orders(self):
503 pass
504
505 @unittest.skip("TODO")
506 def test__repr(self):
507 pass
508
509 def tearDown(self):
510 self.patcher.stop()
511
512 if __name__ == '__main__':
513 unittest.main()