]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Add test for Trade
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
1 import portfolio
2 import unittest
3 from decimal import Decimal as D
4 from unittest import mock
5
6 class AmountTest(unittest.TestCase):
7 def test_values(self):
8 amount = portfolio.Amount("BTC", "0.65")
9 self.assertEqual(D("0.65"), amount.value)
10 self.assertEqual("BTC", amount.currency)
11
12 def test_in_currency(self):
13 amount = portfolio.Amount("ETC", 10)
14
15 self.assertEqual(amount, amount.in_currency("ETC", None))
16
17 ticker_mock = unittest.mock.Mock()
18 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
19 ticker_mock.return_value = None
20
21 self.assertRaises(Exception, amount.in_currency, "ETH", None)
22
23 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
24 ticker_mock.return_value = {
25 "bid": D("0.2"),
26 "ask": D("0.4"),
27 "average": D("0.3"),
28 "foo": "bar",
29 }
30 converted_amount = amount.in_currency("ETH", None)
31
32 self.assertEqual(D("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 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
38 self.assertEqual(D("2"), converted_amount.value)
39
40 converted_amount = amount.in_currency("ETH", None, compute_value="ask")
41 self.assertEqual(D("4"), converted_amount.value)
42
43 converted_amount = amount.in_currency("ETH", None, rate=D("0.02"))
44 self.assertEqual(D("0.2"), converted_amount.value)
45
46 def test__abs(self):
47 amount = portfolio.Amount("SC", -120)
48 self.assertEqual(120, abs(amount).value)
49 self.assertEqual("SC", abs(amount).currency)
50
51 amount = portfolio.Amount("SC", 10)
52 self.assertEqual(10, abs(amount).value)
53 self.assertEqual("SC", abs(amount).currency)
54
55 def test__add(self):
56 amount1 = portfolio.Amount("XVG", "12.9")
57 amount2 = portfolio.Amount("XVG", "13.1")
58
59 self.assertEqual(26, (amount1 + amount2).value)
60 self.assertEqual("XVG", (amount1 + amount2).currency)
61
62 amount3 = portfolio.Amount("ETH", "1.6")
63 with self.assertRaises(Exception):
64 amount1 + amount3
65
66 amount4 = portfolio.Amount("ETH", 0.0)
67 self.assertEqual(amount1, amount1 + amount4)
68
69 def test__radd(self):
70 amount = portfolio.Amount("XVG", "12.9")
71
72 self.assertEqual(amount, 0 + amount)
73 with self.assertRaises(Exception):
74 4 + amount
75
76 def test__sub(self):
77 amount1 = portfolio.Amount("XVG", "13.3")
78 amount2 = portfolio.Amount("XVG", "13.1")
79
80 self.assertEqual(D("0.2"), (amount1 - amount2).value)
81 self.assertEqual("XVG", (amount1 - amount2).currency)
82
83 amount3 = portfolio.Amount("ETH", "1.6")
84 with self.assertRaises(Exception):
85 amount1 - amount3
86
87 amount4 = portfolio.Amount("ETH", 0.0)
88 self.assertEqual(amount1, amount1 - amount4)
89
90 def test__mul(self):
91 amount = portfolio.Amount("XEM", 11)
92
93 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
94 self.assertEqual(D("33"), (amount * 3).value)
95
96 with self.assertRaises(Exception):
97 amount * amount
98
99 def test__rmul(self):
100 amount = portfolio.Amount("XEM", 11)
101
102 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
103 self.assertEqual(D("33"), (3 * amount).value)
104
105 def test__floordiv(self):
106 amount = portfolio.Amount("XEM", 11)
107
108 self.assertEqual(D("5.5"), (amount / 2).value)
109 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
110
111 def test__div(self):
112 amount = portfolio.Amount("XEM", 11)
113
114 self.assertEqual(D("5.5"), (amount / 2).value)
115 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
116
117 def test__lt(self):
118 amount1 = portfolio.Amount("BTD", 11.3)
119 amount2 = portfolio.Amount("BTD", 13.1)
120
121 self.assertTrue(amount1 < amount2)
122 self.assertFalse(amount2 < amount1)
123 self.assertFalse(amount1 < amount1)
124
125 amount3 = portfolio.Amount("BTC", 1.6)
126 with self.assertRaises(Exception):
127 amount1 < amount3
128
129 def test__eq(self):
130 amount1 = portfolio.Amount("BTD", 11.3)
131 amount2 = portfolio.Amount("BTD", 13.1)
132 amount3 = portfolio.Amount("BTD", 11.3)
133
134 self.assertFalse(amount1 == amount2)
135 self.assertFalse(amount2 == amount1)
136 self.assertTrue(amount1 == amount3)
137 self.assertFalse(amount2 == 0)
138
139 amount4 = portfolio.Amount("BTC", 1.6)
140 with self.assertRaises(Exception):
141 amount1 == amount4
142
143 amount5 = portfolio.Amount("BTD", 0)
144 self.assertTrue(amount5 == 0)
145
146 def test__str(self):
147 amount1 = portfolio.Amount("BTX", 32)
148 self.assertEqual("32.00000000 BTX", str(amount1))
149
150 amount2 = portfolio.Amount("USDT", 12000)
151 amount1.linked_to = amount2
152 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
153
154 def test__repr(self):
155 amount1 = portfolio.Amount("BTX", 32)
156 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
157
158 amount2 = portfolio.Amount("USDT", 12000)
159 amount1.linked_to = amount2
160 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
161
162 amount3 = portfolio.Amount("BTC", 0.1)
163 amount2.linked_to = amount3
164 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
165
166 class PortfolioTest(unittest.TestCase):
167 import urllib3
168 def fill_data(self):
169 if self.json_response is not None:
170 portfolio.Portfolio.data = self.json_response
171
172 def setUp(self):
173 super(PortfolioTest, self).setUp()
174
175 with open("test_portfolio.json") as example:
176 import json
177 self.json_response = json.load(example)
178
179 self.patcher = mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={})
180 self.patcher.start()
181
182 @mock.patch.object(urllib3, "disable_warnings")
183 @mock.patch.object(urllib3.poolmanager.PoolManager, "request")
184 @mock.patch.object(portfolio.Portfolio, "URL", new="foo://bar")
185 def test_get_cryptoportfolio(self, request, disable_warnings):
186 request.side_effect = [
187 type('', (), { "data": '{ "foo": "bar" }' }),
188 type('', (), { "data": 'System Error' }),
189 Exception("Connection error"),
190 ]
191
192 portfolio.Portfolio.get_cryptoportfolio()
193 self.assertIn("foo", portfolio.Portfolio.data)
194 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
195 request.assert_called_with("GET", "foo://bar")
196
197 request.reset_mock()
198 portfolio.Portfolio.get_cryptoportfolio()
199 self.assertIsNone(portfolio.Portfolio.data)
200 request.assert_called_with("GET", "foo://bar")
201
202 request.reset_mock()
203 portfolio.Portfolio.data = "foo"
204 portfolio.Portfolio.get_cryptoportfolio()
205 request.assert_called_with("GET", "foo://bar")
206 self.assertEqual("foo", portfolio.Portfolio.data)
207 disable_warnings.assert_called_with()
208
209 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
210 def test_parse_cryptoportfolio(self, mock_get):
211 mock_get.side_effect = self.fill_data
212
213 portfolio.Portfolio.parse_cryptoportfolio()
214
215 self.assertListEqual(
216 ["medium", "high"],
217 list(portfolio.Portfolio.liquidities.keys()))
218
219 liquidities = portfolio.Portfolio.liquidities
220 self.assertEqual(10, len(liquidities["medium"].keys()))
221 self.assertEqual(10, len(liquidities["high"].keys()))
222
223 expected = {'BTC': 2857, 'DGB': 1015, 'DOGE': 1805, 'SC': 623, 'ZEC': 3701}
224 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
225
226 expected = {'ETC': 1000, 'FCT': 1000, 'GAS': 1000, 'NAV': 1000, 'OMG': 1000, 'OMNI': 1000, 'PPC': 1000, 'RIC': 1000, 'VIA': 1000, 'XCP': 1000}
227 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
228
229 # It doesn't refetch the data when available
230 portfolio.Portfolio.parse_cryptoportfolio()
231 mock_get.assert_called_once_with()
232
233 portfolio.Portfolio.data["portfolio_1"]["holding"]["direction"][3] = "short"
234 self.assertRaises(AssertionError, portfolio.Portfolio.parse_cryptoportfolio)
235
236 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
237 def test_repartition_pertenthousand(self, mock_get):
238 mock_get.side_effect = self.fill_data
239
240 expected_medium = {'USDT': 1000, 'ETC': 1000, 'FCT': 1000, 'OMG': 1000, 'STEEM': 1000, 'STRAT': 1000, 'XEM': 1000, 'XMR': 1000, 'XVC': 1000, 'ZRX': 1000}
241 expected_high = {'USDT': 1226, 'BTC': 1429, 'ETC': 1127, 'ETH': 1569, 'FCT': 3341, 'GAS': 1308}
242
243 self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand())
244 self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand(liquidity="medium"))
245 self.assertEqual(expected_high, portfolio.Portfolio.repartition_pertenthousand(liquidity="high"))
246
247 def tearDown(self):
248 self.patcher.stop()
249
250 class BalanceTest(unittest.TestCase):
251 def setUp(self):
252 super(BalanceTest, self).setUp()
253
254 self.fetch_balance = {
255 "free": "foo",
256 "info": "bar",
257 "used": "baz",
258 "total": "bazz",
259 "ETC": {
260 "free": 0.0,
261 "used": 0.0,
262 "total": 0.0
263 },
264 "USDT": {
265 "free": 6.0,
266 "used": 1.2,
267 "total": 7.2
268 },
269 "XVG": {
270 "free": 16,
271 "used": 0.0,
272 "total": 16
273 },
274 "XMR": {
275 "free": 0.0,
276 "used": 0.0,
277 "total": 0.0
278 },
279 }
280 self.patcher = mock.patch.multiple(portfolio.Balance, known_balances={})
281 self.patcher.start()
282
283 def test_values(self):
284 balance = portfolio.Balance("BTC", 0.65, 0.35, 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 balance = portfolio.Balance.from_hash("BTC", { "total": 0.65, "free": 0.35, "used": 0.30})
291 self.assertEqual(0.65, balance.total.value)
292 self.assertEqual(0.35, balance.free.value)
293 self.assertEqual(0.30, balance.used.value)
294 self.assertEqual("BTC", balance.currency)
295
296 @mock.patch.object(portfolio.Trade, "get_ticker")
297 def test_in_currency(self, get_ticker):
298 portfolio.Balance.known_balances = {
299 "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
300 "ETH": portfolio.Balance("ETH", 3, 3, 0),
301 }
302 market = mock.Mock()
303 get_ticker.return_value = {
304 "bid": D("0.09"),
305 "ask": D("0.11"),
306 "average": D("0.1"),
307 }
308
309 amounts = portfolio.Balance.in_currency("BTC", market)
310 self.assertEqual("BTC", amounts["ETH"].currency)
311 self.assertEqual(D("0.65"), amounts["BTC"].value)
312 self.assertEqual(D("0.30"), amounts["ETH"].value)
313
314 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid")
315 self.assertEqual(D("0.65"), amounts["BTC"].value)
316 self.assertEqual(D("0.27"), amounts["ETH"].value)
317
318 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="used")
319 self.assertEqual(D("0.30"), amounts["BTC"].value)
320 self.assertEqual(0, amounts["ETH"].value)
321
322 def test_currencies(self):
323 portfolio.Balance.known_balances = {
324 "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
325 "ETH": portfolio.Balance("ETH", 3, 3, 0),
326 }
327 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
328
329 @mock.patch.object(portfolio.market, "fetch_balance")
330 def test_fetch_balances(self, fetch_balance):
331 fetch_balance.return_value = self.fetch_balance
332
333 portfolio.Balance.fetch_balances(portfolio.market)
334 self.assertNotIn("XMR", portfolio.Balance.currencies())
335 self.assertListEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
336
337 portfolio.Balance.known_balances["ETC"] = portfolio.Balance("ETC", "1", "0", "1")
338 portfolio.Balance.fetch_balances(portfolio.market)
339 self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total)
340 self.assertListEqual(["USDT", "XVG", "ETC"], list(portfolio.Balance.currencies()))
341
342 @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
343 @mock.patch.object(portfolio.market, "fetch_balance")
344 def test_dispatch_assets(self, fetch_balance, repartition):
345 fetch_balance.return_value = self.fetch_balance
346 portfolio.Balance.fetch_balances(portfolio.market)
347
348 self.assertNotIn("XEM", portfolio.Balance.currencies())
349
350 repartition.return_value = {
351 "XEM": 7500,
352 "BTC": 2600,
353 }
354
355 amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1"))
356 self.assertIn("XEM", portfolio.Balance.currencies())
357 self.assertEqual(D("2.6"), amounts["BTC"].value)
358 self.assertEqual(D("7.5"), amounts["XEM"].value)
359
360 @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
361 @mock.patch.object(portfolio.Trade, "get_ticker")
362 @mock.patch.object(portfolio.Trade, "compute_trades")
363 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
364 repartition.return_value = {
365 "XEM": 7500,
366 "BTC": 2500,
367 }
368 def _get_ticker(c1, c2, market):
369 if c1 == "USDT" and c2 == "BTC":
370 return { "average": D("0.0001") }
371 if c1 == "XVG" and c2 == "BTC":
372 return { "average": D("0.000001") }
373 if c1 == "XEM" and c2 == "BTC":
374 return { "average": D("0.001") }
375 self.fail("Should be called with {}, {}".format(c1, c2))
376 get_ticker.side_effect = _get_ticker
377
378 market = mock.Mock()
379 market.fetch_balance.return_value = {
380 "USDT": {
381 "free": D("10000.0"),
382 "used": D("0.0"),
383 "total": D("10000.0")
384 },
385 "XVG": {
386 "free": D("10000.0"),
387 "used": D("0.0"),
388 "total": D("10000.0")
389 },
390 }
391 portfolio.Balance.prepare_trades(market)
392 compute_trades.assert_called()
393
394 call = compute_trades.call_args
395 self.assertEqual(market, call[1]["market"])
396 self.assertEqual(1, call[0][0]["USDT"].value)
397 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
398 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
399 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
400
401 @unittest.skip("TODO")
402 def test_update_trades(self):
403 pass
404
405 def test__repr(self):
406 balance = portfolio.Balance("BTX", 3, 1, 2)
407 self.assertEqual("Balance(BTX [1.00000000 BTX/2.00000000 BTX/3.00000000 BTX])", repr(balance))
408
409 def tearDown(self):
410 self.patcher.stop()
411
412 class TradeTest(unittest.TestCase):
413 import time
414
415 def setUp(self):
416 super(TradeTest, self).setUp()
417
418 self.patcher = mock.patch.multiple(portfolio.Trade,
419 ticker_cache={},
420 ticker_cache_timestamp=self.time.time(),
421 fees_cache={},
422 trades={})
423 self.patcher.start()
424
425 def test_get_ticker(self):
426 market = mock.Mock()
427 market.fetch_ticker.side_effect = [
428 { "bid": 1, "ask": 3 },
429 portfolio.ExchangeError("foo"),
430 { "bid": 10, "ask": 40 },
431 portfolio.ExchangeError("foo"),
432 portfolio.ExchangeError("foo"),
433 ]
434
435 ticker = portfolio.Trade.get_ticker("ETH", "ETC", market)
436 market.fetch_ticker.assert_called_with("ETH/ETC")
437 self.assertEqual(1, ticker["bid"])
438 self.assertEqual(3, ticker["ask"])
439 self.assertEqual(2, ticker["average"])
440 self.assertFalse(ticker["inverted"])
441
442 ticker = portfolio.Trade.get_ticker("ETH", "XVG", market)
443 self.assertEqual(0.0625, ticker["average"])
444 self.assertTrue(ticker["inverted"])
445 self.assertIn("original", ticker)
446 self.assertEqual(10, ticker["original"]["bid"])
447
448 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market)
449 self.assertIsNone(ticker)
450
451 market.fetch_ticker.assert_has_calls([
452 mock.call("ETH/ETC"),
453 mock.call("ETH/XVG"),
454 mock.call("XVG/ETH"),
455 mock.call("XVG/XMR"),
456 mock.call("XMR/XVG"),
457 ])
458
459 market2 = mock.Mock()
460 market2.fetch_ticker.side_effect = [
461 { "bid": 1, "ask": 3 },
462 { "bid": 1.2, "ask": 3.5 },
463 ]
464 ticker1 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
465 ticker2 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
466 ticker3 = portfolio.Trade.get_ticker("ETC", "ETH", market2)
467 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
468 self.assertEqual(1, ticker1["bid"])
469 self.assertDictEqual(ticker1, ticker2)
470 self.assertDictEqual(ticker1, ticker3["original"])
471
472 ticker4 = portfolio.Trade.get_ticker("ETH", "ETC", market2, refresh=True)
473 ticker5 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
474 self.assertEqual(1.2, ticker4["bid"])
475 self.assertDictEqual(ticker4, ticker5)
476
477 market3 = mock.Mock()
478 market3.fetch_ticker.side_effect = [
479 { "bid": 1, "ask": 3 },
480 { "bid": 1.2, "ask": 3.5 },
481 ]
482 ticker6 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
483 portfolio.Trade.ticker_cache_timestamp -= 4
484 ticker7 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
485 portfolio.Trade.ticker_cache_timestamp -= 2
486 ticker8 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
487 self.assertDictEqual(ticker6, ticker7)
488 self.assertEqual(1.2, ticker8["bid"])
489
490 @unittest.skip("TODO")
491 def test_values_assertion(self):
492 value_from = Amount("BTC", "1.0")
493 value_from.linked_to = Amount("ETH", "10.0")
494 value_to = Amount("BTC", "1.0")
495 trade = portfolioTrade(value_from, value_to, "ETH")
496 self.assertEqual("BTC", trade.base_currency)
497 self.assertEqual("ETH", trade.currency)
498
499 with self.assertRaises(AssertionError):
500 portfolio.Trade(value_from, value_to, "ETC")
501 with self.assertRaises(AssertionError):
502 value_from.linked_to = None
503 portfolio.Trade(value_from, value_to, "ETH")
504 with self.assertRaises(AssertionError):
505 value_from.currency = "ETH"
506 portfolio.Trade(value_from, value_to, "ETH")
507
508 @unittest.skip("TODO")
509 def test_fetch_fees(self):
510 pass
511
512 @unittest.skip("TODO")
513 def test_compute_trades(self):
514 pass
515
516 @unittest.skip("TODO")
517 def test_action(self):
518 pass
519
520 @unittest.skip("TODO")
521 def test_action(self):
522 pass
523
524 @unittest.skip("TODO")
525 def test_order_action(self):
526 pass
527
528 @unittest.skip("TODO")
529 def test_prepare_order(self):
530 pass
531
532 @unittest.skip("TODO")
533 def test_all_orders(self):
534 pass
535
536 @unittest.skip("TODO")
537 def test_follow_orders(self):
538 pass
539
540 @unittest.skip("TODO")
541 def test_compute_value(self):
542 pass
543
544 @unittest.skip("TODO")
545 def test__repr(self):
546 pass
547
548 def tearDown(self):
549 self.patcher.stop()
550
551 class AcceptanceTest(unittest.TestCase):
552 import time
553
554 def setUp(self):
555 super(AcceptanceTest, self).setUp()
556
557 self.patchers = [
558 mock.patch.multiple(portfolio.Balance, known_balances={}),
559 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
560 mock.patch.multiple(portfolio.Trade,
561 ticker_cache={},
562 ticker_cache_timestamp=self.time.time(),
563 fees_cache={},
564 trades={}),
565 mock.patch.multiple(portfolio.Computation,
566 computations=portfolio.Computation.computations)
567 ]
568 for patcher in self.patchers:
569 patcher.start()
570
571 def test_success_sell_only_necessary(self):
572 fetch_balance = {
573 "ETH": {
574 "free": D("1.0"),
575 "used": D("0.0"),
576 "total": D("1.0"),
577 },
578 "ETC": {
579 "free": D("4.0"),
580 "used": D("0.0"),
581 "total": D("4.0"),
582 },
583 "XVG": {
584 "free": D("1000.0"),
585 "used": D("0.0"),
586 "total": D("1000.0"),
587 },
588 }
589 repartition = {
590 "ETH": 2500,
591 "ETC": 2500,
592 "BTC": 4000,
593 "BTD": 500,
594 "USDT": 500,
595 }
596
597 def fetch_ticker(symbol):
598 if symbol == "ETH/BTC":
599 return {
600 "symbol": "ETH/BTC",
601 "bid": D("0.14"),
602 "ask": D("0.16")
603 }
604 if symbol == "ETC/BTC":
605 return {
606 "symbol": "ETC/BTC",
607 "bid": D("0.002"),
608 "ask": D("0.003")
609 }
610 if symbol == "XVG/BTC":
611 return {
612 "symbol": "XVG/BTC",
613 "bid": D("0.00003"),
614 "ask": D("0.00005")
615 }
616 if symbol == "BTD/BTC":
617 return {
618 "symbol": "BTD/BTC",
619 "bid": D("0.0008"),
620 "ask": D("0.0012")
621 }
622 if symbol == "USDT/BTC":
623 raise portfolio.ExchangeError
624 if symbol == "BTC/USDT":
625 return {
626 "symbol": "BTC/USDT",
627 "bid": D("14000"),
628 "ask": D("16000")
629 }
630 self.fail("Shouldn't have been called with {}".format(symbol))
631
632 market = mock.Mock()
633 market.fetch_balance.return_value = fetch_balance
634 market.fetch_ticker.side_effect = fetch_ticker
635 with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition):
636 # Action 1
637 portfolio.Balance.prepare_trades(market)
638
639 balances = portfolio.Balance.known_balances
640 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
641 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
642 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
643
644
645 trades = portfolio.Trade.trades
646 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
647 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
648 self.assertEqual("sell", trades["ETH"].action)
649
650 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
651 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETC"].value_to)
652 self.assertEqual("buy", trades["ETC"].action)
653
654 self.assertNotIn("BTC", trades)
655
656 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
657 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["BTD"].value_to)
658 self.assertEqual("buy", trades["BTD"].action)
659
660 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
661 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["USDT"].value_to)
662 self.assertEqual("buy", trades["USDT"].action)
663
664 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from)
665 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
666 self.assertEqual("sell", trades["XVG"].action)
667
668 # Action 2
669 portfolio.Trade.prepare_orders(only="sell", compute_value=lambda x, y: x["bid"] * D("1.001"))
670
671 all_orders = portfolio.Trade.all_orders()
672 self.assertEqual(2, len(all_orders))
673 self.assertEqual(2, 3*all_orders[0].amount.value)
674 self.assertEqual(D("0.14014"), all_orders[0].rate)
675 self.assertEqual(1000, all_orders[1].amount.value)
676 self.assertEqual(D("0.00003003"), all_orders[1].rate)
677
678
679 def create_order(symbol, type, action, amount, price=None):
680 self.assertEqual("limit", type)
681 if symbol == "ETH/BTC":
682 self.assertEqual("sell", action)
683 self.assertEqual(2, 3*amount)
684 self.assertEqual(D("0.14014"), price)
685 elif symbol == "XVG/BTC":
686 self.assertEqual("sell", action)
687 self.assertEqual(1000, amount)
688 self.assertEqual(D("0.00003003"), price)
689 else:
690 self.fail("I shouldn't have been called")
691
692 return {
693 "id": symbol,
694 }
695 market.create_order.side_effect = create_order
696
697 # Action 3
698 portfolio.Trade.run_orders()
699
700 self.assertEqual("open", all_orders[0].status)
701 self.assertEqual("open", all_orders[1].status)
702
703 market.fetch_order.return_value = { "status": "closed" }
704 with mock.patch.object(portfolio.time, "sleep") as sleep:
705 # Action 4
706 portfolio.Trade.follow_orders(verbose=False)
707
708 sleep.assert_called_with(30)
709
710 for order in all_orders:
711 self.assertEqual("closed", order.status)
712
713 fetch_balance = {
714 "ETH": {
715 "free": D("1.0") / 3,
716 "used": D("0.0"),
717 "total": D("1.0") / 3,
718 },
719 "BTC": {
720 "free": D("0.134"),
721 "used": D("0.0"),
722 "total": D("0.134"),
723 },
724 "ETC": {
725 "free": D("4.0"),
726 "used": D("0.0"),
727 "total": D("4.0"),
728 },
729 "XVG": {
730 "free": D("0.0"),
731 "used": D("0.0"),
732 "total": D("0.0"),
733 },
734 }
735 market.fetch_balance.return_value = fetch_balance
736
737 with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition):
738 # Action 5
739 portfolio.Balance.update_trades(market, only="buy", compute_value="average")
740
741 balances = portfolio.Balance.known_balances
742 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
743 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
744 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
745 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
746
747
748 trades = portfolio.Trade.trades
749 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
750 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
751 self.assertEqual("sell", trades["ETH"].action)
752
753 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
754 self.assertEqual(portfolio.Amount("BTC", D("0.0485")), trades["ETC"].value_to)
755 self.assertEqual("buy", trades["ETC"].action)
756
757 self.assertNotIn("BTC", trades)
758
759 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
760 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["BTD"].value_to)
761 self.assertEqual("buy", trades["BTD"].action)
762
763 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
764 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["USDT"].value_to)
765 self.assertEqual("buy", trades["USDT"].action)
766
767 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from)
768 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
769 self.assertEqual("sell", trades["XVG"].action)
770
771 # Action 6
772 portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"])
773
774
775 all_orders = portfolio.Trade.all_orders(state="pending")
776 self.assertEqual(3, len(all_orders))
777 self.assertEqual(portfolio.Amount("ETC", D("38.5")/3), all_orders[0].amount)
778 self.assertEqual(D("0.003"), all_orders[0].rate)
779 self.assertEqual("buy", all_orders[0].action)
780
781 self.assertEqual(portfolio.Amount("BTD", D("24.25")/3), all_orders[1].amount)
782 self.assertEqual(D("0.0012"), all_orders[1].rate)
783 self.assertEqual("buy", all_orders[1].action)
784
785 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[2].amount)
786 self.assertEqual(D("16000"), all_orders[2].rate)
787 self.assertEqual("sell", all_orders[2].action)
788
789 with mock.patch.object(portfolio.time, "sleep") as sleep:
790 # Action 7
791 portfolio.Trade.follow_orders(verbose=False)
792
793 sleep.assert_called_with(30)
794
795 def tearDown(self):
796 for patcher in self.patchers:
797 patcher.stop()
798
799 if __name__ == '__main__':
800 unittest.main()