]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Fix Amount.__sub__ not working with 0
[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 import requests
6 import requests_mock
7 from io import StringIO
8
9 class WebMockTestCase(unittest.TestCase):
10 import time
11
12 def setUp(self):
13 super(WebMockTestCase, self).setUp()
14 self.wm = requests_mock.Mocker()
15 self.wm.start()
16
17 self.patchers = [
18 mock.patch.multiple(portfolio.Balance, known_balances={}),
19 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
20 mock.patch.multiple(portfolio.Trade,
21 ticker_cache={},
22 ticker_cache_timestamp=self.time.time(),
23 fees_cache={},
24 debug=False,
25 trades=[]),
26 mock.patch.multiple(portfolio.Computation,
27 computations=portfolio.Computation.computations)
28 ]
29 for patcher in self.patchers:
30 patcher.start()
31
32
33 def tearDown(self):
34 for patcher in self.patchers:
35 patcher.stop()
36 self.wm.stop()
37 super(WebMockTestCase, self).tearDown()
38
39 class PortfolioTest(WebMockTestCase):
40 def fill_data(self):
41 if self.json_response is not None:
42 portfolio.Portfolio.data = self.json_response
43
44 def setUp(self):
45 super(PortfolioTest, self).setUp()
46
47 with open("test_portfolio.json") as example:
48 self.json_response = example.read()
49
50 self.wm.get(portfolio.Portfolio.URL, text=self.json_response)
51
52 def test_get_cryptoportfolio(self):
53 self.wm.get(portfolio.Portfolio.URL, [
54 {"text":'{ "foo": "bar" }', "status_code": 200},
55 {"text": "System Error", "status_code": 500},
56 {"exc": requests.exceptions.ConnectTimeout},
57 ])
58 portfolio.Portfolio.get_cryptoportfolio()
59 self.assertIn("foo", portfolio.Portfolio.data)
60 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
61 self.assertTrue(self.wm.called)
62 self.assertEqual(1, self.wm.call_count)
63
64 portfolio.Portfolio.get_cryptoportfolio()
65 self.assertIsNone(portfolio.Portfolio.data)
66 self.assertEqual(2, self.wm.call_count)
67
68 portfolio.Portfolio.data = "Foo"
69 portfolio.Portfolio.get_cryptoportfolio()
70 self.assertEqual("Foo", portfolio.Portfolio.data)
71 self.assertEqual(3, self.wm.call_count)
72
73 def test_parse_cryptoportfolio(self):
74 portfolio.Portfolio.parse_cryptoportfolio()
75
76 self.assertListEqual(
77 ["medium", "high"],
78 list(portfolio.Portfolio.liquidities.keys()))
79
80 liquidities = portfolio.Portfolio.liquidities
81 self.assertEqual(10, len(liquidities["medium"].keys()))
82 self.assertEqual(10, len(liquidities["high"].keys()))
83
84 expected = {
85 'BTC': (D("0.2857"), "long"),
86 'DGB': (D("0.1015"), "long"),
87 'DOGE': (D("0.1805"), "long"),
88 'SC': (D("0.0623"), "long"),
89 'ZEC': (D("0.3701"), "long"),
90 }
91 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
92
93 expected = {
94 'BTC': (D("1.1102e-16"), "long"),
95 'ETC': (D("0.1"), "long"),
96 'FCT': (D("0.1"), "long"),
97 'GAS': (D("0.1"), "long"),
98 'NAV': (D("0.1"), "long"),
99 'OMG': (D("0.1"), "long"),
100 'OMNI': (D("0.1"), "long"),
101 'PPC': (D("0.1"), "long"),
102 'RIC': (D("0.1"), "long"),
103 'VIA': (D("0.1"), "long"),
104 'XCP': (D("0.1"), "long"),
105 }
106 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
107
108 # It doesn't refetch the data when available
109 portfolio.Portfolio.parse_cryptoportfolio()
110
111 self.assertEqual(1, self.wm.call_count)
112
113 def test_repartition(self):
114 expected_medium = {
115 'BTC': (D("1.1102e-16"), "long"),
116 'USDT': (D("0.1"), "long"),
117 'ETC': (D("0.1"), "long"),
118 'FCT': (D("0.1"), "long"),
119 'OMG': (D("0.1"), "long"),
120 'STEEM': (D("0.1"), "long"),
121 'STRAT': (D("0.1"), "long"),
122 'XEM': (D("0.1"), "long"),
123 'XMR': (D("0.1"), "long"),
124 'XVC': (D("0.1"), "long"),
125 'ZRX': (D("0.1"), "long"),
126 }
127 expected_high = {
128 'USDT': (D("0.1226"), "long"),
129 'BTC': (D("0.1429"), "long"),
130 'ETC': (D("0.1127"), "long"),
131 'ETH': (D("0.1569"), "long"),
132 'FCT': (D("0.3341"), "long"),
133 'GAS': (D("0.1308"), "long"),
134 }
135
136 self.assertEqual(expected_medium, portfolio.Portfolio.repartition())
137 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
138 self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))
139
140 class AmountTest(WebMockTestCase):
141 def test_values(self):
142 amount = portfolio.Amount("BTC", "0.65")
143 self.assertEqual(D("0.65"), amount.value)
144 self.assertEqual("BTC", amount.currency)
145
146 def test_in_currency(self):
147 amount = portfolio.Amount("ETC", 10)
148
149 self.assertEqual(amount, amount.in_currency("ETC", None))
150
151 ticker_mock = unittest.mock.Mock()
152 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
153 ticker_mock.return_value = None
154
155 self.assertRaises(Exception, amount.in_currency, "ETH", None)
156
157 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
158 ticker_mock.return_value = {
159 "bid": D("0.2"),
160 "ask": D("0.4"),
161 "average": D("0.3"),
162 "foo": "bar",
163 }
164 converted_amount = amount.in_currency("ETH", None)
165
166 self.assertEqual(D("3.0"), converted_amount.value)
167 self.assertEqual("ETH", converted_amount.currency)
168 self.assertEqual(amount, converted_amount.linked_to)
169 self.assertEqual("bar", converted_amount.ticker["foo"])
170
171 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
172 self.assertEqual(D("2"), converted_amount.value)
173
174 converted_amount = amount.in_currency("ETH", None, compute_value="ask")
175 self.assertEqual(D("4"), converted_amount.value)
176
177 converted_amount = amount.in_currency("ETH", None, rate=D("0.02"))
178 self.assertEqual(D("0.2"), converted_amount.value)
179
180 def test__round(self):
181 amount = portfolio.Amount("BAR", portfolio.D("1.23456789876"))
182 self.assertEqual(D("1.23456789"), round(amount).value)
183 self.assertEqual(D("1.23"), round(amount, 2).value)
184
185 def test__abs(self):
186 amount = portfolio.Amount("SC", -120)
187 self.assertEqual(120, abs(amount).value)
188 self.assertEqual("SC", abs(amount).currency)
189
190 amount = portfolio.Amount("SC", 10)
191 self.assertEqual(10, abs(amount).value)
192 self.assertEqual("SC", abs(amount).currency)
193
194 def test__add(self):
195 amount1 = portfolio.Amount("XVG", "12.9")
196 amount2 = portfolio.Amount("XVG", "13.1")
197
198 self.assertEqual(26, (amount1 + amount2).value)
199 self.assertEqual("XVG", (amount1 + amount2).currency)
200
201 amount3 = portfolio.Amount("ETH", "1.6")
202 with self.assertRaises(Exception):
203 amount1 + amount3
204
205 amount4 = portfolio.Amount("ETH", 0.0)
206 self.assertEqual(amount1, amount1 + amount4)
207
208 def test__radd(self):
209 amount = portfolio.Amount("XVG", "12.9")
210
211 self.assertEqual(amount, 0 + amount)
212 with self.assertRaises(Exception):
213 4 + amount
214
215 def test__sub(self):
216 amount1 = portfolio.Amount("XVG", "13.3")
217 amount2 = portfolio.Amount("XVG", "13.1")
218
219 self.assertEqual(D("0.2"), (amount1 - amount2).value)
220 self.assertEqual("XVG", (amount1 - amount2).currency)
221
222 amount3 = portfolio.Amount("ETH", "1.6")
223 with self.assertRaises(Exception):
224 amount1 - amount3
225
226 amount4 = portfolio.Amount("ETH", 0.0)
227 self.assertEqual(amount1, amount1 - amount4)
228
229 def test__mul(self):
230 amount = portfolio.Amount("XEM", 11)
231
232 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
233 self.assertEqual(D("33"), (amount * 3).value)
234
235 with self.assertRaises(Exception):
236 amount * amount
237
238 def test__rmul(self):
239 amount = portfolio.Amount("XEM", 11)
240
241 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
242 self.assertEqual(D("33"), (3 * amount).value)
243
244 def test__floordiv(self):
245 amount = portfolio.Amount("XEM", 11)
246
247 self.assertEqual(D("5.5"), (amount / 2).value)
248 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
249
250 def test__truediv(self):
251 amount = portfolio.Amount("XEM", 11)
252
253 self.assertEqual(D("5.5"), (amount / 2).value)
254 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
255
256 def test__lt(self):
257 amount1 = portfolio.Amount("BTD", 11.3)
258 amount2 = portfolio.Amount("BTD", 13.1)
259
260 self.assertTrue(amount1 < amount2)
261 self.assertFalse(amount2 < amount1)
262 self.assertFalse(amount1 < amount1)
263
264 amount3 = portfolio.Amount("BTC", 1.6)
265 with self.assertRaises(Exception):
266 amount1 < amount3
267
268 def test__le(self):
269 amount1 = portfolio.Amount("BTD", 11.3)
270 amount2 = portfolio.Amount("BTD", 13.1)
271
272 self.assertTrue(amount1 <= amount2)
273 self.assertFalse(amount2 <= amount1)
274 self.assertTrue(amount1 <= amount1)
275
276 amount3 = portfolio.Amount("BTC", 1.6)
277 with self.assertRaises(Exception):
278 amount1 <= amount3
279
280 def test__gt(self):
281 amount1 = portfolio.Amount("BTD", 11.3)
282 amount2 = portfolio.Amount("BTD", 13.1)
283
284 self.assertTrue(amount2 > amount1)
285 self.assertFalse(amount1 > amount2)
286 self.assertFalse(amount1 > amount1)
287
288 amount3 = portfolio.Amount("BTC", 1.6)
289 with self.assertRaises(Exception):
290 amount3 > amount1
291
292 def test__ge(self):
293 amount1 = portfolio.Amount("BTD", 11.3)
294 amount2 = portfolio.Amount("BTD", 13.1)
295
296 self.assertTrue(amount2 >= amount1)
297 self.assertFalse(amount1 >= amount2)
298 self.assertTrue(amount1 >= amount1)
299
300 amount3 = portfolio.Amount("BTC", 1.6)
301 with self.assertRaises(Exception):
302 amount3 >= amount1
303
304 def test__eq(self):
305 amount1 = portfolio.Amount("BTD", 11.3)
306 amount2 = portfolio.Amount("BTD", 13.1)
307 amount3 = portfolio.Amount("BTD", 11.3)
308
309 self.assertFalse(amount1 == amount2)
310 self.assertFalse(amount2 == amount1)
311 self.assertTrue(amount1 == amount3)
312 self.assertFalse(amount2 == 0)
313
314 amount4 = portfolio.Amount("BTC", 1.6)
315 with self.assertRaises(Exception):
316 amount1 == amount4
317
318 amount5 = portfolio.Amount("BTD", 0)
319 self.assertTrue(amount5 == 0)
320
321 def test__ne(self):
322 amount1 = portfolio.Amount("BTD", 11.3)
323 amount2 = portfolio.Amount("BTD", 13.1)
324 amount3 = portfolio.Amount("BTD", 11.3)
325
326 self.assertTrue(amount1 != amount2)
327 self.assertTrue(amount2 != amount1)
328 self.assertFalse(amount1 != amount3)
329 self.assertTrue(amount2 != 0)
330
331 amount4 = portfolio.Amount("BTC", 1.6)
332 with self.assertRaises(Exception):
333 amount1 != amount4
334
335 amount5 = portfolio.Amount("BTD", 0)
336 self.assertFalse(amount5 != 0)
337
338 def test__neg(self):
339 amount1 = portfolio.Amount("BTD", "11.3")
340
341 self.assertEqual(portfolio.D("-11.3"), (-amount1).value)
342
343 def test__str(self):
344 amount1 = portfolio.Amount("BTX", 32)
345 self.assertEqual("32.00000000 BTX", str(amount1))
346
347 amount2 = portfolio.Amount("USDT", 12000)
348 amount1.linked_to = amount2
349 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
350
351 def test__repr(self):
352 amount1 = portfolio.Amount("BTX", 32)
353 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
354
355 amount2 = portfolio.Amount("USDT", 12000)
356 amount1.linked_to = amount2
357 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
358
359 amount3 = portfolio.Amount("BTC", 0.1)
360 amount2.linked_to = amount3
361 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
362
363 class BalanceTest(WebMockTestCase):
364 def setUp(self):
365 super(BalanceTest, self).setUp()
366
367 self.fetch_balance = {
368 "ETC": {
369 "exchange_free": 0,
370 "exchange_used": 0,
371 "exchange_total": 0,
372 "margin_total": 0,
373 },
374 "USDT": {
375 "exchange_free": D("6.0"),
376 "exchange_used": D("1.2"),
377 "exchange_total": D("7.2"),
378 "margin_total": 0,
379 },
380 "XVG": {
381 "exchange_free": 16,
382 "exchange_used": 0,
383 "exchange_total": 16,
384 "margin_total": 0,
385 },
386 "XMR": {
387 "exchange_free": 0,
388 "exchange_used": 0,
389 "exchange_total": 0,
390 "margin_total": D("-1.0"),
391 "margin_free": 0,
392 },
393 }
394
395 def test_values(self):
396 balance = portfolio.Balance("BTC", {
397 "exchange_total": "0.65",
398 "exchange_free": "0.35",
399 "exchange_used": "0.30",
400 "margin_total": "-10",
401 "margin_borrowed": "-10",
402 "margin_free": "0",
403 "margin_position_type": "short",
404 "margin_borrowed_base_currency": "USDT",
405 "margin_liquidation_price": "1.20",
406 "margin_pending_gain": "10",
407 "margin_lending_fees": "0.4",
408 "margin_borrowed_base_price": "0.15",
409 })
410 self.assertEqual(portfolio.D("0.65"), balance.exchange_total.value)
411 self.assertEqual(portfolio.D("0.35"), balance.exchange_free.value)
412 self.assertEqual(portfolio.D("0.30"), balance.exchange_used.value)
413 self.assertEqual("BTC", balance.exchange_total.currency)
414 self.assertEqual("BTC", balance.exchange_free.currency)
415 self.assertEqual("BTC", balance.exchange_total.currency)
416
417 self.assertEqual(portfolio.D("-10"), balance.margin_total.value)
418 self.assertEqual(portfolio.D("-10"), balance.margin_borrowed.value)
419 self.assertEqual(portfolio.D("0"), balance.margin_free.value)
420 self.assertEqual("BTC", balance.margin_total.currency)
421 self.assertEqual("BTC", balance.margin_borrowed.currency)
422 self.assertEqual("BTC", balance.margin_free.currency)
423
424 self.assertEqual("BTC", balance.currency)
425
426 self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
427 self.assertEqual("USDT", balance.margin_lending_fees.currency)
428
429 @mock.patch.object(portfolio.Trade, "get_ticker")
430 def test_in_currency(self, get_ticker):
431 portfolio.Balance.known_balances = {
432 "BTC": portfolio.Balance("BTC", {
433 "total": "0.65",
434 "exchange_total":"0.65",
435 "exchange_free": "0.35",
436 "exchange_used": "0.30"}),
437 "ETH": portfolio.Balance("ETH", {
438 "total": 3,
439 "exchange_total": 3,
440 "exchange_free": 3,
441 "exchange_used": 0}),
442 }
443 market = mock.Mock()
444 get_ticker.return_value = {
445 "bid": D("0.09"),
446 "ask": D("0.11"),
447 "average": D("0.1"),
448 }
449
450 amounts = portfolio.Balance.in_currency("BTC", market)
451 self.assertEqual("BTC", amounts["ETH"].currency)
452 self.assertEqual(D("0.65"), amounts["BTC"].value)
453 self.assertEqual(D("0.30"), amounts["ETH"].value)
454
455 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid")
456 self.assertEqual(D("0.65"), amounts["BTC"].value)
457 self.assertEqual(D("0.27"), amounts["ETH"].value)
458
459 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="exchange_used")
460 self.assertEqual(D("0.30"), amounts["BTC"].value)
461 self.assertEqual(0, amounts["ETH"].value)
462
463 def test_currencies(self):
464 portfolio.Balance.known_balances = {
465 "BTC": portfolio.Balance("BTC", {
466 "total": "0.65",
467 "exchange_total":"0.65",
468 "exchange_free": "0.35",
469 "exchange_used": "0.30"}),
470 "ETH": portfolio.Balance("ETH", {
471 "total": 3,
472 "exchange_total": 3,
473 "exchange_free": 3,
474 "exchange_used": 0}),
475 }
476 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
477
478 @mock.patch.object(portfolio.market, "fetch_all_balances")
479 def test_fetch_balances(self, fetch_all_balances):
480 fetch_all_balances.return_value = self.fetch_balance
481
482 portfolio.Balance.fetch_balances(portfolio.market)
483 self.assertNotIn("ETC", portfolio.Balance.currencies())
484 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.Balance.currencies()))
485
486 portfolio.Balance.known_balances["ETC"] = portfolio.Balance("ETC", {
487 "exchange_total": "1", "exchange_free": "0",
488 "exchange_used": "1" })
489 portfolio.Balance.fetch_balances(portfolio.market)
490 self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total)
491 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.Balance.currencies()))
492
493 @mock.patch.object(portfolio.Portfolio, "repartition")
494 @mock.patch.object(portfolio.market, "fetch_all_balances")
495 def test_dispatch_assets(self, fetch_all_balances, repartition):
496 fetch_all_balances.return_value = self.fetch_balance
497 portfolio.Balance.fetch_balances(portfolio.market)
498
499 self.assertNotIn("XEM", portfolio.Balance.currencies())
500
501 repartition.return_value = {
502 "XEM": (D("0.75"), "long"),
503 "BTC": (D("0.26"), "long"),
504 }
505
506 amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1"))
507 self.assertIn("XEM", portfolio.Balance.currencies())
508 self.assertEqual(D("2.6"), amounts["BTC"].value)
509 self.assertEqual(D("7.5"), amounts["XEM"].value)
510
511 @mock.patch.object(portfolio.Portfolio, "repartition")
512 @mock.patch.object(portfolio.Trade, "get_ticker")
513 @mock.patch.object(portfolio.Trade, "compute_trades")
514 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
515 repartition.return_value = {
516 "XEM": (D("0.75"), "long"),
517 "BTC": (D("0.25"), "long"),
518 }
519 def _get_ticker(c1, c2, market):
520 if c1 == "USDT" and c2 == "BTC":
521 return { "average": D("0.0001") }
522 if c1 == "XVG" and c2 == "BTC":
523 return { "average": D("0.000001") }
524 if c1 == "XEM" and c2 == "BTC":
525 return { "average": D("0.001") }
526 self.fail("Should be called with {}, {}".format(c1, c2))
527 get_ticker.side_effect = _get_ticker
528
529 market = mock.Mock()
530 market.fetch_all_balances.return_value = {
531 "USDT": {
532 "exchange_free": D("10000.0"),
533 "exchange_used": D("0.0"),
534 "exchange_total": D("10000.0"),
535 "total": D("10000.0")
536 },
537 "XVG": {
538 "exchange_free": D("10000.0"),
539 "exchange_used": D("0.0"),
540 "exchange_total": D("10000.0"),
541 "total": D("10000.0")
542 },
543 }
544 portfolio.Balance.prepare_trades(market)
545 compute_trades.assert_called()
546
547 call = compute_trades.call_args
548 self.assertEqual(market, call[1]["market"])
549 self.assertEqual(1, call[0][0]["USDT"].value)
550 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
551 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
552 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
553
554 @mock.patch.object(portfolio.Portfolio, "repartition")
555 @mock.patch.object(portfolio.Trade, "get_ticker")
556 @mock.patch.object(portfolio.Trade, "compute_trades")
557 def test_update_trades(self, compute_trades, get_ticker, repartition):
558 repartition.return_value = {
559 "XEM": (D("0.75"), "long"),
560 "BTC": (D("0.25"), "long"),
561 }
562 def _get_ticker(c1, c2, market):
563 if c1 == "USDT" and c2 == "BTC":
564 return { "average": D("0.0001") }
565 if c1 == "XVG" and c2 == "BTC":
566 return { "average": D("0.000001") }
567 if c1 == "XEM" and c2 == "BTC":
568 return { "average": D("0.001") }
569 self.fail("Should be called with {}, {}".format(c1, c2))
570 get_ticker.side_effect = _get_ticker
571
572 market = mock.Mock()
573 market.fetch_all_balances.return_value = {
574 "USDT": {
575 "exchange_free": D("10000.0"),
576 "exchange_used": D("0.0"),
577 "exchange_total": D("10000.0"),
578 "total": D("10000.0")
579 },
580 "XVG": {
581 "exchange_free": D("10000.0"),
582 "exchange_used": D("0.0"),
583 "exchange_total": D("10000.0"),
584 "total": D("10000.0")
585 },
586 }
587 portfolio.Balance.update_trades(market)
588 compute_trades.assert_called()
589
590 call = compute_trades.call_args
591 self.assertEqual(market, call[1]["market"])
592 self.assertEqual(1, call[0][0]["USDT"].value)
593 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
594 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
595 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
596
597 @mock.patch.object(portfolio.Portfolio, "repartition")
598 @mock.patch.object(portfolio.Trade, "get_ticker")
599 @mock.patch.object(portfolio.Trade, "compute_trades")
600 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
601 def _get_ticker(c1, c2, market):
602 if c1 == "USDT" and c2 == "BTC":
603 return { "average": D("0.0001") }
604 if c1 == "XVG" and c2 == "BTC":
605 return { "average": D("0.000001") }
606 self.fail("Should be called with {}, {}".format(c1, c2))
607 get_ticker.side_effect = _get_ticker
608
609 market = mock.Mock()
610 market.fetch_all_balances.return_value = {
611 "USDT": {
612 "exchange_free": D("10000.0"),
613 "exchange_used": D("0.0"),
614 "exchange_total": D("10000.0"),
615 "total": D("10000.0")
616 },
617 "XVG": {
618 "exchange_free": D("10000.0"),
619 "exchange_used": D("0.0"),
620 "exchange_total": D("10000.0"),
621 "total": D("10000.0")
622 },
623 }
624 portfolio.Balance.prepare_trades_to_sell_all(market)
625 repartition.assert_not_called()
626 compute_trades.assert_called()
627
628 call = compute_trades.call_args
629 self.assertEqual(market, call[1]["market"])
630 self.assertEqual(1, call[0][0]["USDT"].value)
631 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
632 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
633
634 def test__repr(self):
635 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
636 repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
637 balance = portfolio.Balance("BTX", { "exchange_total": 3,
638 "exchange_used": 1, "exchange_free": 2 })
639 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
640
641 balance = portfolio.Balance("BTX", { "margin_total": 3,
642 "margin_borrowed": 1, "margin_free": 2 })
643 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance))
644
645 balance = portfolio.Balance("BTX", { "margin_total": -3,
646 "margin_borrowed_base_price": D("0.1"),
647 "margin_borrowed_base_currency": "BTC",
648 "margin_lending_fees": D("0.002") })
649 self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
650
651 class TradeTest(WebMockTestCase):
652
653 def test_get_ticker(self):
654 market = mock.Mock()
655 market.fetch_ticker.side_effect = [
656 { "bid": 1, "ask": 3 },
657 portfolio.ExchangeError("foo"),
658 { "bid": 10, "ask": 40 },
659 portfolio.ExchangeError("foo"),
660 portfolio.ExchangeError("foo"),
661 ]
662
663 ticker = portfolio.Trade.get_ticker("ETH", "ETC", market)
664 market.fetch_ticker.assert_called_with("ETH/ETC")
665 self.assertEqual(1, ticker["bid"])
666 self.assertEqual(3, ticker["ask"])
667 self.assertEqual(2, ticker["average"])
668 self.assertFalse(ticker["inverted"])
669
670 ticker = portfolio.Trade.get_ticker("ETH", "XVG", market)
671 self.assertEqual(0.0625, ticker["average"])
672 self.assertTrue(ticker["inverted"])
673 self.assertIn("original", ticker)
674 self.assertEqual(10, ticker["original"]["bid"])
675
676 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market)
677 self.assertIsNone(ticker)
678
679 market.fetch_ticker.assert_has_calls([
680 mock.call("ETH/ETC"),
681 mock.call("ETH/XVG"),
682 mock.call("XVG/ETH"),
683 mock.call("XVG/XMR"),
684 mock.call("XMR/XVG"),
685 ])
686
687 market2 = mock.Mock()
688 market2.fetch_ticker.side_effect = [
689 { "bid": 1, "ask": 3 },
690 { "bid": 1.2, "ask": 3.5 },
691 ]
692 ticker1 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
693 ticker2 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
694 ticker3 = portfolio.Trade.get_ticker("ETC", "ETH", market2)
695 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
696 self.assertEqual(1, ticker1["bid"])
697 self.assertDictEqual(ticker1, ticker2)
698 self.assertDictEqual(ticker1, ticker3["original"])
699
700 ticker4 = portfolio.Trade.get_ticker("ETH", "ETC", market2, refresh=True)
701 ticker5 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
702 self.assertEqual(1.2, ticker4["bid"])
703 self.assertDictEqual(ticker4, ticker5)
704
705 market3 = mock.Mock()
706 market3.fetch_ticker.side_effect = [
707 { "bid": 1, "ask": 3 },
708 { "bid": 1.2, "ask": 3.5 },
709 ]
710 ticker6 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
711 portfolio.Trade.ticker_cache_timestamp -= 4
712 ticker7 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
713 portfolio.Trade.ticker_cache_timestamp -= 2
714 ticker8 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
715 self.assertDictEqual(ticker6, ticker7)
716 self.assertEqual(1.2, ticker8["bid"])
717
718 def test_values_assertion(self):
719 value_from = portfolio.Amount("BTC", "1.0")
720 value_from.linked_to = portfolio.Amount("ETH", "10.0")
721 value_to = portfolio.Amount("BTC", "1.0")
722 trade = portfolio.Trade(value_from, value_to, "ETH")
723 self.assertEqual("BTC", trade.base_currency)
724 self.assertEqual("ETH", trade.currency)
725
726 with self.assertRaises(AssertionError):
727 portfolio.Trade(value_from, value_to, "ETC")
728 with self.assertRaises(AssertionError):
729 value_from.linked_to = None
730 portfolio.Trade(value_from, value_to, "ETH")
731 with self.assertRaises(AssertionError):
732 value_from.currency = "ETH"
733 portfolio.Trade(value_from, value_to, "ETH")
734
735 value_from = portfolio.Amount("BTC", 0)
736 trade = portfolio.Trade(value_from, value_to, "ETH")
737 self.assertEqual(0, trade.value_from.linked_to)
738
739 def test_fetch_fees(self):
740 market = mock.Mock()
741 market.fetch_fees.return_value = "Foo"
742 self.assertEqual("Foo", portfolio.Trade.fetch_fees(market))
743 market.fetch_fees.assert_called_once()
744 self.assertEqual("Foo", portfolio.Trade.fetch_fees(market))
745 market.fetch_fees.assert_called_once()
746
747 def test_action(self):
748 value_from = portfolio.Amount("BTC", "1.0")
749 value_from.linked_to = portfolio.Amount("ETH", "10.0")
750 value_to = portfolio.Amount("BTC", "1.0")
751 trade = portfolio.Trade(value_from, value_to, "ETH")
752
753 self.assertIsNone(trade.action)
754
755 value_from = portfolio.Amount("BTC", "1.0")
756 value_from.linked_to = portfolio.Amount("BTC", "1.0")
757 value_to = portfolio.Amount("BTC", "1.0")
758 trade = portfolio.Trade(value_from, value_to, "BTC")
759
760 self.assertIsNone(trade.action)
761
762 value_from = portfolio.Amount("BTC", "0.5")
763 value_from.linked_to = portfolio.Amount("ETH", "10.0")
764 value_to = portfolio.Amount("BTC", "1.0")
765 trade = portfolio.Trade(value_from, value_to, "ETH")
766
767 self.assertEqual("acquire", trade.action)
768
769 value_from = portfolio.Amount("BTC", "0")
770 value_from.linked_to = portfolio.Amount("ETH", "0")
771 value_to = portfolio.Amount("BTC", "-1.0")
772 trade = portfolio.Trade(value_from, value_to, "ETH")
773
774 self.assertEqual("dispose", trade.action)
775
776 def test_order_action(self):
777 value_from = portfolio.Amount("BTC", "0.5")
778 value_from.linked_to = portfolio.Amount("ETH", "10.0")
779 value_to = portfolio.Amount("BTC", "1.0")
780 trade = portfolio.Trade(value_from, value_to, "ETH")
781
782 self.assertEqual("buy", trade.order_action(False))
783 self.assertEqual("sell", trade.order_action(True))
784
785 value_from = portfolio.Amount("BTC", "0")
786 value_from.linked_to = portfolio.Amount("ETH", "0")
787 value_to = portfolio.Amount("BTC", "-1.0")
788 trade = portfolio.Trade(value_from, value_to, "ETH")
789
790 self.assertEqual("sell", trade.order_action(False))
791 self.assertEqual("buy", trade.order_action(True))
792
793 def test_trade_type(self):
794 value_from = portfolio.Amount("BTC", "0.5")
795 value_from.linked_to = portfolio.Amount("ETH", "10.0")
796 value_to = portfolio.Amount("BTC", "1.0")
797 trade = portfolio.Trade(value_from, value_to, "ETH")
798
799 self.assertEqual("long", trade.trade_type)
800
801 value_from = portfolio.Amount("BTC", "0")
802 value_from.linked_to = portfolio.Amount("ETH", "0")
803 value_to = portfolio.Amount("BTC", "-1.0")
804 trade = portfolio.Trade(value_from, value_to, "ETH")
805
806 self.assertEqual("short", trade.trade_type)
807
808 def test_filled_amount(self):
809 value_from = portfolio.Amount("BTC", "0.5")
810 value_from.linked_to = portfolio.Amount("ETH", "10.0")
811 value_to = portfolio.Amount("BTC", "1.0")
812 trade = portfolio.Trade(value_from, value_to, "ETH")
813
814 order1 = mock.Mock()
815 order1.filled_amount = portfolio.Amount("ETH", "0.3")
816
817 order2 = mock.Mock()
818 order2.filled_amount = portfolio.Amount("ETH", "0.01")
819 trade.orders.append(order1)
820 trade.orders.append(order2)
821
822 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount)
823
824 def test_prepare_orders(self):
825 trade_mock1 = mock.Mock()
826 trade_mock2 = mock.Mock()
827
828 portfolio.Trade.trades.append(trade_mock1)
829 portfolio.Trade.trades.append(trade_mock2)
830
831 portfolio.Trade.prepare_orders()
832 trade_mock1.prepare_order.assert_called_with(compute_value="default")
833 trade_mock2.prepare_order.assert_called_with(compute_value="default")
834
835 portfolio.Trade.prepare_orders(compute_value="bla")
836 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
837 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
838
839 trade_mock1.prepare_order.reset_mock()
840 trade_mock2.prepare_order.reset_mock()
841
842 trade_mock1.action = "foo"
843 trade_mock2.action = "bar"
844 portfolio.Trade.prepare_orders(only="bar")
845 trade_mock1.prepare_order.assert_not_called()
846 trade_mock2.prepare_order.assert_called_with(compute_value="default")
847
848 @unittest.skip("TODO")
849 def test_compute_trades(self):
850 pass
851
852 @unittest.skip("TODO")
853 def test_prepare_order(self):
854 pass
855
856 @unittest.skip("TODO")
857 def test_update_order(self):
858 pass
859
860 @unittest.skip("TODO")
861 def test_follow_orders(self):
862 pass
863
864 @unittest.skip("TODO")
865 def test_move_balances(self):
866 pass
867
868 def test_all_orders(self):
869 trade_mock1 = mock.Mock()
870 trade_mock2 = mock.Mock()
871
872 order_mock1 = mock.Mock()
873 order_mock2 = mock.Mock()
874 order_mock3 = mock.Mock()
875
876 trade_mock1.orders = [order_mock1, order_mock2]
877 trade_mock2.orders = [order_mock3]
878
879 order_mock1.status = "pending"
880 order_mock2.status = "open"
881 order_mock3.status = "open"
882
883 portfolio.Trade.trades.append(trade_mock1)
884 portfolio.Trade.trades.append(trade_mock2)
885
886 orders = portfolio.Trade.all_orders()
887 self.assertEqual(3, len(orders))
888
889 open_orders = portfolio.Trade.all_orders(state="open")
890 self.assertEqual(2, len(open_orders))
891 self.assertEqual([order_mock2, order_mock3], open_orders)
892
893 @mock.patch.object(portfolio.Trade, "all_orders")
894 def test_run_orders(self, all_orders):
895 order_mock1 = mock.Mock()
896 order_mock2 = mock.Mock()
897 order_mock3 = mock.Mock()
898 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
899 portfolio.Trade.run_orders()
900 all_orders.assert_called_with(state="pending")
901
902 order_mock1.run.assert_called()
903 order_mock2.run.assert_called()
904 order_mock3.run.assert_called()
905
906 @mock.patch.object(portfolio.Trade, "all_orders")
907 def test_update_all_orders_status(self, all_orders):
908 order_mock1 = mock.Mock()
909 order_mock2 = mock.Mock()
910 order_mock3 = mock.Mock()
911 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
912 portfolio.Trade.update_all_orders_status()
913 all_orders.assert_called_with(state="open")
914
915 order_mock1.get_status.assert_called()
916 order_mock2.get_status.assert_called()
917 order_mock3.get_status.assert_called()
918
919 def test_print_all_with_order(self):
920 trade_mock1 = mock.Mock()
921 trade_mock2 = mock.Mock()
922 trade_mock3 = mock.Mock()
923 portfolio.Trade.trades = [trade_mock1, trade_mock2, trade_mock3]
924
925 portfolio.Trade.print_all_with_order()
926
927 trade_mock1.print_with_order.assert_called()
928 trade_mock2.print_with_order.assert_called()
929 trade_mock3.print_with_order.assert_called()
930
931 @mock.patch('sys.stdout', new_callable=StringIO)
932 def test_print_with_order(self, mock_stdout):
933 value_from = portfolio.Amount("BTC", "0.5")
934 value_from.linked_to = portfolio.Amount("ETH", "10.0")
935 value_to = portfolio.Amount("BTC", "1.0")
936 trade = portfolio.Trade(value_from, value_to, "ETH")
937
938 order_mock1 = mock.Mock()
939 order_mock1.__repr__ = mock.Mock()
940 order_mock1.__repr__.return_value = "Mock 1"
941 order_mock2 = mock.Mock()
942 order_mock2.__repr__ = mock.Mock()
943 order_mock2.__repr__.return_value = "Mock 2"
944 trade.orders.append(order_mock1)
945 trade.orders.append(order_mock2)
946
947 trade.print_with_order()
948
949 out = mock_stdout.getvalue().split("\n")
950 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0])
951 self.assertEqual("\tMock 1", out[1])
952 self.assertEqual("\tMock 2", out[2])
953
954 def test_compute_value(self):
955 compute = mock.Mock()
956 portfolio.Trade.compute_value("foo", "buy", compute_value=compute)
957 compute.assert_called_with("foo", "ask")
958
959 compute.reset_mock()
960 portfolio.Trade.compute_value("foo", "sell", compute_value=compute)
961 compute.assert_called_with("foo", "bid")
962
963 compute.reset_mock()
964 portfolio.Trade.compute_value("foo", "ask", compute_value=compute)
965 compute.assert_called_with("foo", "ask")
966
967 compute.reset_mock()
968 portfolio.Trade.compute_value("foo", "bid", compute_value=compute)
969 compute.assert_called_with("foo", "bid")
970
971 compute.reset_mock()
972 portfolio.Computation.computations["test"] = compute
973 portfolio.Trade.compute_value("foo", "bid", compute_value="test")
974 compute.assert_called_with("foo", "bid")
975
976 def test__repr(self):
977 value_from = portfolio.Amount("BTC", "0.5")
978 value_from.linked_to = portfolio.Amount("ETH", "10.0")
979 value_to = portfolio.Amount("BTC", "1.0")
980 trade = portfolio.Trade(value_from, value_to, "ETH")
981
982 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
983
984 class AcceptanceTest(WebMockTestCase):
985 @unittest.expectedFailure
986 def test_success_sell_only_necessary(self):
987 fetch_balance = {
988 "ETH": {
989 "exchange_free": D("1.0"),
990 "exchange_used": D("0.0"),
991 "exchange_total": D("1.0"),
992 "total": D("1.0"),
993 },
994 "ETC": {
995 "exchange_free": D("4.0"),
996 "exchange_used": D("0.0"),
997 "exchange_total": D("4.0"),
998 "total": D("4.0"),
999 },
1000 "XVG": {
1001 "exchange_free": D("1000.0"),
1002 "exchange_used": D("0.0"),
1003 "exchange_total": D("1000.0"),
1004 "total": D("1000.0"),
1005 },
1006 }
1007 repartition = {
1008 "ETH": (D("0.25"), "long"),
1009 "ETC": (D("0.25"), "long"),
1010 "BTC": (D("0.4"), "long"),
1011 "BTD": (D("0.01"), "short"),
1012 "B2X": (D("0.04"), "long"),
1013 "USDT": (D("0.05"), "long"),
1014 }
1015
1016 def fetch_ticker(symbol):
1017 if symbol == "ETH/BTC":
1018 return {
1019 "symbol": "ETH/BTC",
1020 "bid": D("0.14"),
1021 "ask": D("0.16")
1022 }
1023 if symbol == "ETC/BTC":
1024 return {
1025 "symbol": "ETC/BTC",
1026 "bid": D("0.002"),
1027 "ask": D("0.003")
1028 }
1029 if symbol == "XVG/BTC":
1030 return {
1031 "symbol": "XVG/BTC",
1032 "bid": D("0.00003"),
1033 "ask": D("0.00005")
1034 }
1035 if symbol == "BTD/BTC":
1036 return {
1037 "symbol": "BTD/BTC",
1038 "bid": D("0.0008"),
1039 "ask": D("0.0012")
1040 }
1041 if symbol == "B2X/BTC":
1042 return {
1043 "symbol": "B2X/BTC",
1044 "bid": D("0.0008"),
1045 "ask": D("0.0012")
1046 }
1047 if symbol == "USDT/BTC":
1048 raise portfolio.ExchangeError
1049 if symbol == "BTC/USDT":
1050 return {
1051 "symbol": "BTC/USDT",
1052 "bid": D("14000"),
1053 "ask": D("16000")
1054 }
1055 self.fail("Shouldn't have been called with {}".format(symbol))
1056
1057 market = mock.Mock()
1058 market.fetch_all_balances.return_value = fetch_balance
1059 market.fetch_ticker.side_effect = fetch_ticker
1060 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
1061 # Action 1
1062 portfolio.Balance.prepare_trades(market)
1063
1064 balances = portfolio.Balance.known_balances
1065 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
1066 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
1067 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
1068
1069
1070 trades = portfolio.Trade.trades
1071 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
1072 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
1073 self.assertEqual("dispose", trades[0].action)
1074
1075 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
1076 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
1077 self.assertEqual("acquire", trades[1].action)
1078
1079 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
1080 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
1081 self.assertEqual("dispose", trades[2].action)
1082
1083 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
1084 self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
1085 self.assertEqual("dispose", trades[3].action)
1086
1087 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
1088 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
1089 self.assertEqual("acquire", trades[4].action)
1090
1091 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
1092 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
1093 self.assertEqual("acquire", trades[5].action)
1094
1095 # Action 2
1096 portfolio.Trade.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001"))
1097
1098 all_orders = portfolio.Trade.all_orders()
1099 self.assertEqual(2, len(all_orders))
1100 self.assertEqual(2, 3*all_orders[0].amount.value)
1101 self.assertEqual(D("0.14014"), all_orders[0].rate)
1102 self.assertEqual(1000, all_orders[1].amount.value)
1103 self.assertEqual(D("0.00003003"), all_orders[1].rate)
1104
1105
1106 def create_order(symbol, type, action, amount, price=None, account="exchange"):
1107 self.assertEqual("limit", type)
1108 if symbol == "ETH/BTC":
1109 self.assertEqual("sell", action)
1110 self.assertEqual(D('0.66666666'), amount)
1111 self.assertEqual(D("0.14014"), price)
1112 elif symbol == "XVG/BTC":
1113 self.assertEqual("sell", action)
1114 self.assertEqual(1000, amount)
1115 self.assertEqual(D("0.00003003"), price)
1116 else:
1117 self.fail("I shouldn't have been called")
1118
1119 return {
1120 "id": symbol,
1121 }
1122 market.create_order.side_effect = create_order
1123 market.order_precision.return_value = 8
1124
1125 # Action 3
1126 portfolio.Trade.run_orders()
1127
1128 self.assertEqual("open", all_orders[0].status)
1129 self.assertEqual("open", all_orders[1].status)
1130
1131 market.fetch_order.return_value = { "status": "closed" }
1132 with mock.patch.object(portfolio.time, "sleep") as sleep:
1133 # Action 4
1134 portfolio.Trade.follow_orders(verbose=False)
1135
1136 sleep.assert_called_with(30)
1137
1138 for order in all_orders:
1139 self.assertEqual("closed", order.status)
1140
1141 fetch_balance = {
1142 "ETH": {
1143 "free": D("1.0") / 3,
1144 "used": D("0.0"),
1145 "total": D("1.0") / 3,
1146 },
1147 "BTC": {
1148 "free": D("0.134"),
1149 "used": D("0.0"),
1150 "total": D("0.134"),
1151 },
1152 "ETC": {
1153 "free": D("4.0"),
1154 "used": D("0.0"),
1155 "total": D("4.0"),
1156 },
1157 "XVG": {
1158 "free": D("0.0"),
1159 "used": D("0.0"),
1160 "total": D("0.0"),
1161 },
1162 }
1163 market.fetch_balance.return_value = fetch_balance
1164
1165 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
1166 # Action 5
1167 portfolio.Balance.update_trades(market, only="buy", compute_value="average")
1168
1169 balances = portfolio.Balance.known_balances
1170 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
1171 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
1172 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
1173 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
1174
1175
1176 trades = portfolio.Trade.trades
1177 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
1178 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
1179 self.assertEqual("sell", trades["ETH"].action)
1180
1181 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
1182 self.assertEqual(portfolio.Amount("BTC", D("0.0485")), trades["ETC"].value_to)
1183 self.assertEqual("buy", trades["ETC"].action)
1184
1185 self.assertNotIn("BTC", trades)
1186
1187 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
1188 self.assertEqual(portfolio.Amount("BTC", D("0.00194")), trades["BTD"].value_to)
1189 self.assertEqual("buy", trades["BTD"].action)
1190
1191 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from)
1192 self.assertEqual(portfolio.Amount("BTC", D("0.00776")), trades["B2X"].value_to)
1193 self.assertEqual("buy", trades["B2X"].action)
1194
1195 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
1196 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["USDT"].value_to)
1197 self.assertEqual("buy", trades["USDT"].action)
1198
1199 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from)
1200 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
1201 self.assertEqual("sell", trades["XVG"].action)
1202
1203 # Action 6
1204 portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"])
1205
1206 all_orders = portfolio.Trade.all_orders(state="pending")
1207 self.assertEqual(4, len(all_orders))
1208 self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount))
1209 self.assertEqual(D("0.003"), all_orders[0].rate)
1210 self.assertEqual("buy", all_orders[0].action)
1211 self.assertEqual("long", all_orders[0].trade_type)
1212
1213 self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount))
1214 self.assertEqual(D("0.0012"), all_orders[1].rate)
1215 self.assertEqual("sell", all_orders[1].action)
1216 self.assertEqual("short", all_orders[1].trade_type)
1217
1218 diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount
1219 self.assertAlmostEqual(0, diff.value)
1220 self.assertEqual(D("0.0012"), all_orders[2].rate)
1221 self.assertEqual("buy", all_orders[2].action)
1222 self.assertEqual("long", all_orders[2].trade_type)
1223
1224 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount)
1225 self.assertEqual(D("16000"), all_orders[3].rate)
1226 self.assertEqual("sell", all_orders[3].action)
1227 self.assertEqual("long", all_orders[3].trade_type)
1228
1229 # Action 6b
1230 # TODO:
1231 # Move balances to margin
1232
1233 # Action 7
1234 # TODO
1235 # portfolio.Trade.run_orders()
1236
1237 with mock.patch.object(portfolio.time, "sleep") as sleep:
1238 # Action 8
1239 portfolio.Trade.follow_orders(verbose=False)
1240
1241 sleep.assert_called_with(30)
1242
1243 if __name__ == '__main__':
1244 unittest.main()