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