]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Work in progress to use shorts
[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, parse_int=portfolio.D, parse_float=portfolio.D)
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 = {
224 'BTC': (D("0.2857"), "long"),
225 'DGB': (D("0.1015"), "long"),
226 'DOGE': (D("0.1805"), "long"),
227 'SC': (D("0.0623"), "long"),
228 'ZEC': (D("0.3701"), "long"),
229 }
230 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
231
232 expected = {
233 'BTC': (D("1.1102e-16"), "long"),
234 'ETC': (D("0.1"), "long"),
235 'FCT': (D("0.1"), "long"),
236 'GAS': (D("0.1"), "long"),
237 'NAV': (D("0.1"), "long"),
238 'OMG': (D("0.1"), "long"),
239 'OMNI': (D("0.1"), "long"),
240 'PPC': (D("0.1"), "long"),
241 'RIC': (D("0.1"), "long"),
242 'VIA': (D("0.1"), "long"),
243 'XCP': (D("0.1"), "long"),
244 }
245 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
246
247 # It doesn't refetch the data when available
248 portfolio.Portfolio.parse_cryptoportfolio()
249 mock_get.assert_called_once_with()
250
251 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
252 def test_repartition(self, mock_get):
253 mock_get.side_effect = self.fill_data
254
255 expected_medium = {
256 'BTC': (D("1.1102e-16"), "long"),
257 'USDT': (D("0.1"), "long"),
258 'ETC': (D("0.1"), "long"),
259 'FCT': (D("0.1"), "long"),
260 'OMG': (D("0.1"), "long"),
261 'STEEM': (D("0.1"), "long"),
262 'STRAT': (D("0.1"), "long"),
263 'XEM': (D("0.1"), "long"),
264 'XMR': (D("0.1"), "long"),
265 'XVC': (D("0.1"), "long"),
266 'ZRX': (D("0.1"), "long"),
267 }
268 expected_high = {
269 'USDT': (D("0.1226"), "long"),
270 'BTC': (D("0.1429"), "long"),
271 'ETC': (D("0.1127"), "long"),
272 'ETH': (D("0.1569"), "long"),
273 'FCT': (D("0.3341"), "long"),
274 'GAS': (D("0.1308"), "long"),
275 }
276
277 self.assertEqual(expected_medium, portfolio.Portfolio.repartition())
278 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
279 self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))
280
281 def tearDown(self):
282 self.patcher.stop()
283
284 class BalanceTest(unittest.TestCase):
285 def setUp(self):
286 super(BalanceTest, self).setUp()
287
288 self.fetch_balance = {
289 "free": "foo",
290 "info": "bar",
291 "used": "baz",
292 "total": "bazz",
293 "ETC": {
294 "free": 0.0,
295 "used": 0.0,
296 "total": 0.0
297 },
298 "USDT": {
299 "free": 6.0,
300 "used": 1.2,
301 "total": 7.2
302 },
303 "XVG": {
304 "free": 16,
305 "used": 0.0,
306 "total": 16
307 },
308 "XMR": {
309 "free": 0.0,
310 "used": 0.0,
311 "total": 0.0
312 },
313 }
314 self.patcher = mock.patch.multiple(portfolio.Balance, known_balances={})
315 self.patcher.start()
316
317 def test_values(self):
318 balance = portfolio.Balance("BTC", 0.65, 0.35, 0.30)
319 self.assertEqual(0.65, balance.total.value)
320 self.assertEqual(0.35, balance.free.value)
321 self.assertEqual(0.30, balance.used.value)
322 self.assertEqual("BTC", balance.currency)
323
324 balance = portfolio.Balance.from_hash("BTC", { "total": 0.65, "free": 0.35, "used": 0.30})
325 self.assertEqual(0.65, balance.total.value)
326 self.assertEqual(0.35, balance.free.value)
327 self.assertEqual(0.30, balance.used.value)
328 self.assertEqual("BTC", balance.currency)
329
330 @mock.patch.object(portfolio.Trade, "get_ticker")
331 def test_in_currency(self, get_ticker):
332 portfolio.Balance.known_balances = {
333 "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
334 "ETH": portfolio.Balance("ETH", 3, 3, 0),
335 }
336 market = mock.Mock()
337 get_ticker.return_value = {
338 "bid": D("0.09"),
339 "ask": D("0.11"),
340 "average": D("0.1"),
341 }
342
343 amounts = portfolio.Balance.in_currency("BTC", market)
344 self.assertEqual("BTC", amounts["ETH"].currency)
345 self.assertEqual(D("0.65"), amounts["BTC"].value)
346 self.assertEqual(D("0.30"), amounts["ETH"].value)
347
348 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid")
349 self.assertEqual(D("0.65"), amounts["BTC"].value)
350 self.assertEqual(D("0.27"), amounts["ETH"].value)
351
352 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="used")
353 self.assertEqual(D("0.30"), amounts["BTC"].value)
354 self.assertEqual(0, amounts["ETH"].value)
355
356 def test_currencies(self):
357 portfolio.Balance.known_balances = {
358 "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
359 "ETH": portfolio.Balance("ETH", 3, 3, 0),
360 }
361 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
362
363 @mock.patch.object(portfolio.market, "fetch_balance")
364 def test_fetch_balances(self, fetch_balance):
365 fetch_balance.return_value = self.fetch_balance
366
367 portfolio.Balance.fetch_balances(portfolio.market)
368 self.assertNotIn("XMR", portfolio.Balance.currencies())
369 self.assertListEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
370
371 portfolio.Balance.known_balances["ETC"] = portfolio.Balance("ETC", "1", "0", "1")
372 portfolio.Balance.fetch_balances(portfolio.market)
373 self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total)
374 self.assertListEqual(["USDT", "XVG", "ETC"], list(portfolio.Balance.currencies()))
375
376 @mock.patch.object(portfolio.Portfolio, "repartition")
377 @mock.patch.object(portfolio.market, "fetch_balance")
378 def test_dispatch_assets(self, fetch_balance, repartition):
379 fetch_balance.return_value = self.fetch_balance
380 portfolio.Balance.fetch_balances(portfolio.market)
381
382 self.assertNotIn("XEM", portfolio.Balance.currencies())
383
384 repartition.return_value = {
385 "XEM": (D("0.75"), "long"),
386 "BTC": (D("0.26"), "long"),
387 }
388
389 amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1"))
390 self.assertIn("XEM", portfolio.Balance.currencies())
391 self.assertEqual(D("2.6"), amounts["BTC"].value)
392 self.assertEqual(D("7.5"), amounts["XEM"].value)
393
394 @mock.patch.object(portfolio.Portfolio, "repartition")
395 @mock.patch.object(portfolio.Trade, "get_ticker")
396 @mock.patch.object(portfolio.Trade, "compute_trades")
397 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
398 repartition.return_value = {
399 "XEM": (D("0.75"), "long"),
400 "BTC": (D("0.25"), "long"),
401 }
402 def _get_ticker(c1, c2, market):
403 if c1 == "USDT" and c2 == "BTC":
404 return { "average": D("0.0001") }
405 if c1 == "XVG" and c2 == "BTC":
406 return { "average": D("0.000001") }
407 if c1 == "XEM" and c2 == "BTC":
408 return { "average": D("0.001") }
409 self.fail("Should be called with {}, {}".format(c1, c2))
410 get_ticker.side_effect = _get_ticker
411
412 market = mock.Mock()
413 market.fetch_balance.return_value = {
414 "USDT": {
415 "free": D("10000.0"),
416 "used": D("0.0"),
417 "total": D("10000.0")
418 },
419 "XVG": {
420 "free": D("10000.0"),
421 "used": D("0.0"),
422 "total": D("10000.0")
423 },
424 }
425 portfolio.Balance.prepare_trades(market)
426 compute_trades.assert_called()
427
428 call = compute_trades.call_args
429 self.assertEqual(market, call[1]["market"])
430 self.assertEqual(1, call[0][0]["USDT"].value)
431 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
432 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
433 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
434
435 @unittest.skip("TODO")
436 def test_update_trades(self):
437 pass
438
439 def test__repr(self):
440 balance = portfolio.Balance("BTX", 3, 1, 2)
441 self.assertEqual("Balance(BTX [1.00000000 BTX/2.00000000 BTX/3.00000000 BTX])", repr(balance))
442
443 def tearDown(self):
444 self.patcher.stop()
445
446 class TradeTest(unittest.TestCase):
447 import time
448
449 def setUp(self):
450 super(TradeTest, self).setUp()
451
452 self.patcher = mock.patch.multiple(portfolio.Trade,
453 ticker_cache={},
454 ticker_cache_timestamp=self.time.time(),
455 fees_cache={},
456 trades={})
457 self.patcher.start()
458
459 def test_get_ticker(self):
460 market = mock.Mock()
461 market.fetch_ticker.side_effect = [
462 { "bid": 1, "ask": 3 },
463 portfolio.ExchangeError("foo"),
464 { "bid": 10, "ask": 40 },
465 portfolio.ExchangeError("foo"),
466 portfolio.ExchangeError("foo"),
467 ]
468
469 ticker = portfolio.Trade.get_ticker("ETH", "ETC", market)
470 market.fetch_ticker.assert_called_with("ETH/ETC")
471 self.assertEqual(1, ticker["bid"])
472 self.assertEqual(3, ticker["ask"])
473 self.assertEqual(2, ticker["average"])
474 self.assertFalse(ticker["inverted"])
475
476 ticker = portfolio.Trade.get_ticker("ETH", "XVG", market)
477 self.assertEqual(0.0625, ticker["average"])
478 self.assertTrue(ticker["inverted"])
479 self.assertIn("original", ticker)
480 self.assertEqual(10, ticker["original"]["bid"])
481
482 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market)
483 self.assertIsNone(ticker)
484
485 market.fetch_ticker.assert_has_calls([
486 mock.call("ETH/ETC"),
487 mock.call("ETH/XVG"),
488 mock.call("XVG/ETH"),
489 mock.call("XVG/XMR"),
490 mock.call("XMR/XVG"),
491 ])
492
493 market2 = mock.Mock()
494 market2.fetch_ticker.side_effect = [
495 { "bid": 1, "ask": 3 },
496 { "bid": 1.2, "ask": 3.5 },
497 ]
498 ticker1 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
499 ticker2 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
500 ticker3 = portfolio.Trade.get_ticker("ETC", "ETH", market2)
501 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
502 self.assertEqual(1, ticker1["bid"])
503 self.assertDictEqual(ticker1, ticker2)
504 self.assertDictEqual(ticker1, ticker3["original"])
505
506 ticker4 = portfolio.Trade.get_ticker("ETH", "ETC", market2, refresh=True)
507 ticker5 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
508 self.assertEqual(1.2, ticker4["bid"])
509 self.assertDictEqual(ticker4, ticker5)
510
511 market3 = mock.Mock()
512 market3.fetch_ticker.side_effect = [
513 { "bid": 1, "ask": 3 },
514 { "bid": 1.2, "ask": 3.5 },
515 ]
516 ticker6 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
517 portfolio.Trade.ticker_cache_timestamp -= 4
518 ticker7 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
519 portfolio.Trade.ticker_cache_timestamp -= 2
520 ticker8 = portfolio.Trade.get_ticker("ETH", "ETC", market3)
521 self.assertDictEqual(ticker6, ticker7)
522 self.assertEqual(1.2, ticker8["bid"])
523
524 @unittest.skip("TODO")
525 def test_values_assertion(self):
526 value_from = Amount("BTC", "1.0")
527 value_from.linked_to = Amount("ETH", "10.0")
528 value_to = Amount("BTC", "1.0")
529 trade = portfolioTrade(value_from, value_to, "ETH")
530 self.assertEqual("BTC", trade.base_currency)
531 self.assertEqual("ETH", trade.currency)
532
533 with self.assertRaises(AssertionError):
534 portfolio.Trade(value_from, value_to, "ETC")
535 with self.assertRaises(AssertionError):
536 value_from.linked_to = None
537 portfolio.Trade(value_from, value_to, "ETH")
538 with self.assertRaises(AssertionError):
539 value_from.currency = "ETH"
540 portfolio.Trade(value_from, value_to, "ETH")
541
542 @unittest.skip("TODO")
543 def test_fetch_fees(self):
544 pass
545
546 @unittest.skip("TODO")
547 def test_compute_trades(self):
548 pass
549
550 @unittest.skip("TODO")
551 def test_action(self):
552 pass
553
554 @unittest.skip("TODO")
555 def test_action(self):
556 pass
557
558 @unittest.skip("TODO")
559 def test_order_action(self):
560 pass
561
562 @unittest.skip("TODO")
563 def test_prepare_order(self):
564 pass
565
566 @unittest.skip("TODO")
567 def test_all_orders(self):
568 pass
569
570 @unittest.skip("TODO")
571 def test_follow_orders(self):
572 pass
573
574 @unittest.skip("TODO")
575 def test_compute_value(self):
576 pass
577
578 @unittest.skip("TODO")
579 def test__repr(self):
580 pass
581
582 def tearDown(self):
583 self.patcher.stop()
584
585 class AcceptanceTest(unittest.TestCase):
586 import time
587
588 def setUp(self):
589 super(AcceptanceTest, self).setUp()
590
591 self.patchers = [
592 mock.patch.multiple(portfolio.Balance, known_balances={}),
593 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
594 mock.patch.multiple(portfolio.Trade,
595 ticker_cache={},
596 ticker_cache_timestamp=self.time.time(),
597 fees_cache={},
598 trades={}),
599 mock.patch.multiple(portfolio.Computation,
600 computations=portfolio.Computation.computations)
601 ]
602 for patcher in self.patchers:
603 patcher.start()
604
605 def test_success_sell_only_necessary(self):
606 fetch_balance = {
607 "ETH": {
608 "free": D("1.0"),
609 "used": D("0.0"),
610 "total": D("1.0"),
611 },
612 "ETC": {
613 "free": D("4.0"),
614 "used": D("0.0"),
615 "total": D("4.0"),
616 },
617 "XVG": {
618 "free": D("1000.0"),
619 "used": D("0.0"),
620 "total": D("1000.0"),
621 },
622 }
623 repartition = {
624 "ETH": (D("0.25"), "long"),
625 "ETC": (D("0.25"), "long"),
626 "BTC": (D("0.4"), "long"),
627 "BTD": (D("0.01"), "short"),
628 "B2X": (D("0.04"), "long"),
629 "USDT": (D("0.05"), "long"),
630 }
631
632 def fetch_ticker(symbol):
633 if symbol == "ETH/BTC":
634 return {
635 "symbol": "ETH/BTC",
636 "bid": D("0.14"),
637 "ask": D("0.16")
638 }
639 if symbol == "ETC/BTC":
640 return {
641 "symbol": "ETC/BTC",
642 "bid": D("0.002"),
643 "ask": D("0.003")
644 }
645 if symbol == "XVG/BTC":
646 return {
647 "symbol": "XVG/BTC",
648 "bid": D("0.00003"),
649 "ask": D("0.00005")
650 }
651 if symbol == "BTD/BTC":
652 return {
653 "symbol": "BTD/BTC",
654 "bid": D("0.0008"),
655 "ask": D("0.0012")
656 }
657 if symbol == "B2X/BTC":
658 return {
659 "symbol": "B2X/BTC",
660 "bid": D("0.0008"),
661 "ask": D("0.0012")
662 }
663 if symbol == "USDT/BTC":
664 raise portfolio.ExchangeError
665 if symbol == "BTC/USDT":
666 return {
667 "symbol": "BTC/USDT",
668 "bid": D("14000"),
669 "ask": D("16000")
670 }
671 self.fail("Shouldn't have been called with {}".format(symbol))
672
673 market = mock.Mock()
674 market.fetch_balance.return_value = fetch_balance
675 market.fetch_ticker.side_effect = fetch_ticker
676 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
677 # Action 1
678 portfolio.Balance.prepare_trades(market)
679
680 balances = portfolio.Balance.known_balances
681 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
682 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
683 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
684
685
686 trades = portfolio.Trade.trades
687 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
688 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
689 self.assertEqual("sell", trades["ETH"].action)
690
691 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
692 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETC"].value_to)
693 self.assertEqual("buy", trades["ETC"].action)
694
695 self.assertNotIn("BTC", trades)
696
697 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
698 self.assertEqual(portfolio.Amount("BTC", D("0.002")), trades["BTD"].value_to)
699 self.assertEqual("buy", trades["BTD"].action)
700
701 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from)
702 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades["B2X"].value_to)
703 self.assertEqual("buy", trades["B2X"].action)
704
705 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
706 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["USDT"].value_to)
707 self.assertEqual("buy", trades["USDT"].action)
708
709 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from)
710 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
711 self.assertEqual("sell", trades["XVG"].action)
712
713 # Action 2
714 portfolio.Trade.prepare_orders(only="sell", compute_value=lambda x, y: x["bid"] * D("1.001"))
715
716 all_orders = portfolio.Trade.all_orders()
717 self.assertEqual(2, len(all_orders))
718 self.assertEqual(2, 3*all_orders[0].amount.value)
719 self.assertEqual(D("0.14014"), all_orders[0].rate)
720 self.assertEqual(1000, all_orders[1].amount.value)
721 self.assertEqual(D("0.00003003"), all_orders[1].rate)
722
723
724 def create_order(symbol, type, action, amount, price=None, account="exchange"):
725 self.assertEqual("limit", type)
726 if symbol == "ETH/BTC":
727 self.assertEqual("sell", action)
728 self.assertEqual(D('0.66666666'), amount)
729 self.assertEqual(D("0.14014"), price)
730 elif symbol == "XVG/BTC":
731 self.assertEqual("sell", action)
732 self.assertEqual(1000, amount)
733 self.assertEqual(D("0.00003003"), price)
734 else:
735 self.fail("I shouldn't have been called")
736
737 return {
738 "id": symbol,
739 }
740 market.create_order.side_effect = create_order
741 market.order_precision.return_value = 8
742
743 # Action 3
744 portfolio.Trade.run_orders()
745
746 self.assertEqual("open", all_orders[0].status)
747 self.assertEqual("open", all_orders[1].status)
748
749 market.fetch_order.return_value = { "status": "closed" }
750 with mock.patch.object(portfolio.time, "sleep") as sleep:
751 # Action 4
752 portfolio.Trade.follow_orders(verbose=False)
753
754 sleep.assert_called_with(30)
755
756 for order in all_orders:
757 self.assertEqual("closed", order.status)
758
759 fetch_balance = {
760 "ETH": {
761 "free": D("1.0") / 3,
762 "used": D("0.0"),
763 "total": D("1.0") / 3,
764 },
765 "BTC": {
766 "free": D("0.134"),
767 "used": D("0.0"),
768 "total": D("0.134"),
769 },
770 "ETC": {
771 "free": D("4.0"),
772 "used": D("0.0"),
773 "total": D("4.0"),
774 },
775 "XVG": {
776 "free": D("0.0"),
777 "used": D("0.0"),
778 "total": D("0.0"),
779 },
780 }
781 market.fetch_balance.return_value = fetch_balance
782
783 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
784 # Action 5
785 portfolio.Balance.update_trades(market, only="buy", compute_value="average")
786
787 balances = portfolio.Balance.known_balances
788 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
789 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
790 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
791 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
792
793
794 trades = portfolio.Trade.trades
795 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
796 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
797 self.assertEqual("sell", trades["ETH"].action)
798
799 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
800 self.assertEqual(portfolio.Amount("BTC", D("0.0485")), trades["ETC"].value_to)
801 self.assertEqual("buy", trades["ETC"].action)
802
803 self.assertNotIn("BTC", trades)
804
805 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
806 self.assertEqual(portfolio.Amount("BTC", D("0.00194")), trades["BTD"].value_to)
807 self.assertEqual("buy", trades["BTD"].action)
808
809 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from)
810 self.assertEqual(portfolio.Amount("BTC", D("0.00776")), trades["B2X"].value_to)
811 self.assertEqual("buy", trades["B2X"].action)
812
813 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
814 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["USDT"].value_to)
815 self.assertEqual("buy", trades["USDT"].action)
816
817 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from)
818 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
819 self.assertEqual("sell", trades["XVG"].action)
820
821 # Action 6
822 portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"])
823
824 all_orders = portfolio.Trade.all_orders(state="pending")
825 self.assertEqual(4, len(all_orders))
826 self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount))
827 self.assertEqual(D("0.003"), all_orders[0].rate)
828 self.assertEqual("buy", all_orders[0].action)
829 self.assertEqual("long", all_orders[0].trade_type)
830
831 self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount))
832 self.assertEqual(D("0.0012"), all_orders[1].rate)
833 self.assertEqual("sell", all_orders[1].action)
834 self.assertEqual("short", all_orders[1].trade_type)
835
836 diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount
837 self.assertAlmostEqual(0, diff.value)
838 self.assertEqual(D("0.0012"), all_orders[2].rate)
839 self.assertEqual("buy", all_orders[2].action)
840 self.assertEqual("long", all_orders[2].trade_type)
841
842 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount)
843 self.assertEqual(D("16000"), all_orders[3].rate)
844 self.assertEqual("sell", all_orders[3].action)
845 self.assertEqual("long", all_orders[3].trade_type)
846
847 # Action 7
848 # TODO
849 # portfolio.Trade.run_orders()
850
851 with mock.patch.object(portfolio.time, "sleep") as sleep:
852 # Action 8
853 portfolio.Trade.follow_orders(verbose=False)
854
855 sleep.assert_called_with(30)
856
857 def tearDown(self):
858 for patcher in self.patchers:
859 patcher.stop()
860
861 if __name__ == '__main__':
862 unittest.main()