]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Add last tests for helper
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
1 import sys
2 import portfolio
3 import unittest
4 from decimal import Decimal as D
5 from unittest import mock
6 import requests
7 import requests_mock
8 from io import StringIO
9 import helper
10
11 limits = ["acceptance", "unit"]
12 for test_type in limits:
13 if "--no{}".format(test_type) in sys.argv:
14 sys.argv.remove("--no{}".format(test_type))
15 limits.remove(test_type)
16 if "--only{}".format(test_type) in sys.argv:
17 sys.argv.remove("--only{}".format(test_type))
18 limits = [test_type]
19 break
20
21 class WebMockTestCase(unittest.TestCase):
22 import time
23
24 def setUp(self):
25 super(WebMockTestCase, self).setUp()
26 self.wm = requests_mock.Mocker()
27 self.wm.start()
28
29 self.patchers = [
30 mock.patch.multiple(portfolio.BalanceStore,
31 all={},),
32 mock.patch.multiple(portfolio.TradeStore,
33 all=[],
34 debug=False),
35 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
36 mock.patch.multiple(portfolio.Computation,
37 computations=portfolio.Computation.computations),
38 mock.patch.multiple(helper,
39 fees_cache={},
40 ticker_cache={},
41 ticker_cache_timestamp=self.time.time()),
42 ]
43 for patcher in self.patchers:
44 patcher.start()
45
46 def tearDown(self):
47 for patcher in self.patchers:
48 patcher.stop()
49 self.wm.stop()
50 super(WebMockTestCase, self).tearDown()
51
52 @unittest.skipUnless("unit" in limits, "Unit skipped")
53 class PortfolioTest(WebMockTestCase):
54 def fill_data(self):
55 if self.json_response is not None:
56 portfolio.Portfolio.data = self.json_response
57
58 def setUp(self):
59 super(PortfolioTest, self).setUp()
60
61 with open("test_portfolio.json") as example:
62 self.json_response = example.read()
63
64 self.wm.get(portfolio.Portfolio.URL, text=self.json_response)
65
66 def test_get_cryptoportfolio(self):
67 self.wm.get(portfolio.Portfolio.URL, [
68 {"text":'{ "foo": "bar" }', "status_code": 200},
69 {"text": "System Error", "status_code": 500},
70 {"exc": requests.exceptions.ConnectTimeout},
71 ])
72 portfolio.Portfolio.get_cryptoportfolio()
73 self.assertIn("foo", portfolio.Portfolio.data)
74 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
75 self.assertTrue(self.wm.called)
76 self.assertEqual(1, self.wm.call_count)
77
78 portfolio.Portfolio.get_cryptoportfolio()
79 self.assertIsNone(portfolio.Portfolio.data)
80 self.assertEqual(2, self.wm.call_count)
81
82 portfolio.Portfolio.data = "Foo"
83 portfolio.Portfolio.get_cryptoportfolio()
84 self.assertEqual("Foo", portfolio.Portfolio.data)
85 self.assertEqual(3, self.wm.call_count)
86
87 def test_parse_cryptoportfolio(self):
88 portfolio.Portfolio.parse_cryptoportfolio()
89
90 self.assertListEqual(
91 ["medium", "high"],
92 list(portfolio.Portfolio.liquidities.keys()))
93
94 liquidities = portfolio.Portfolio.liquidities
95 self.assertEqual(10, len(liquidities["medium"].keys()))
96 self.assertEqual(10, len(liquidities["high"].keys()))
97
98 expected = {
99 'BTC': (D("0.2857"), "long"),
100 'DGB': (D("0.1015"), "long"),
101 'DOGE': (D("0.1805"), "long"),
102 'SC': (D("0.0623"), "long"),
103 'ZEC': (D("0.3701"), "long"),
104 }
105 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
106
107 expected = {
108 'BTC': (D("1.1102e-16"), "long"),
109 'ETC': (D("0.1"), "long"),
110 'FCT': (D("0.1"), "long"),
111 'GAS': (D("0.1"), "long"),
112 'NAV': (D("0.1"), "long"),
113 'OMG': (D("0.1"), "long"),
114 'OMNI': (D("0.1"), "long"),
115 'PPC': (D("0.1"), "long"),
116 'RIC': (D("0.1"), "long"),
117 'VIA': (D("0.1"), "long"),
118 'XCP': (D("0.1"), "long"),
119 }
120 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
121
122 # It doesn't refetch the data when available
123 portfolio.Portfolio.parse_cryptoportfolio()
124
125 self.assertEqual(1, self.wm.call_count)
126
127 def test_repartition(self):
128 expected_medium = {
129 'BTC': (D("1.1102e-16"), "long"),
130 'USDT': (D("0.1"), "long"),
131 'ETC': (D("0.1"), "long"),
132 'FCT': (D("0.1"), "long"),
133 'OMG': (D("0.1"), "long"),
134 'STEEM': (D("0.1"), "long"),
135 'STRAT': (D("0.1"), "long"),
136 'XEM': (D("0.1"), "long"),
137 'XMR': (D("0.1"), "long"),
138 'XVC': (D("0.1"), "long"),
139 'ZRX': (D("0.1"), "long"),
140 }
141 expected_high = {
142 'USDT': (D("0.1226"), "long"),
143 'BTC': (D("0.1429"), "long"),
144 'ETC': (D("0.1127"), "long"),
145 'ETH': (D("0.1569"), "long"),
146 'FCT': (D("0.3341"), "long"),
147 'GAS': (D("0.1308"), "long"),
148 }
149
150 self.assertEqual(expected_medium, portfolio.Portfolio.repartition())
151 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
152 self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))
153
154 @unittest.skipUnless("unit" in limits, "Unit skipped")
155 class AmountTest(WebMockTestCase):
156 def test_values(self):
157 amount = portfolio.Amount("BTC", "0.65")
158 self.assertEqual(D("0.65"), amount.value)
159 self.assertEqual("BTC", amount.currency)
160
161 def test_in_currency(self):
162 amount = portfolio.Amount("ETC", 10)
163
164 self.assertEqual(amount, amount.in_currency("ETC", None))
165
166 ticker_mock = unittest.mock.Mock()
167 with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
168 ticker_mock.return_value = None
169
170 self.assertRaises(Exception, amount.in_currency, "ETH", None)
171
172 with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
173 ticker_mock.return_value = {
174 "bid": D("0.2"),
175 "ask": D("0.4"),
176 "average": D("0.3"),
177 "foo": "bar",
178 }
179 converted_amount = amount.in_currency("ETH", None)
180
181 self.assertEqual(D("3.0"), converted_amount.value)
182 self.assertEqual("ETH", converted_amount.currency)
183 self.assertEqual(amount, converted_amount.linked_to)
184 self.assertEqual("bar", converted_amount.ticker["foo"])
185
186 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
187 self.assertEqual(D("2"), converted_amount.value)
188
189 converted_amount = amount.in_currency("ETH", None, compute_value="ask")
190 self.assertEqual(D("4"), converted_amount.value)
191
192 converted_amount = amount.in_currency("ETH", None, rate=D("0.02"))
193 self.assertEqual(D("0.2"), converted_amount.value)
194
195 def test__round(self):
196 amount = portfolio.Amount("BAR", portfolio.D("1.23456789876"))
197 self.assertEqual(D("1.23456789"), round(amount).value)
198 self.assertEqual(D("1.23"), round(amount, 2).value)
199
200 def test__abs(self):
201 amount = portfolio.Amount("SC", -120)
202 self.assertEqual(120, abs(amount).value)
203 self.assertEqual("SC", abs(amount).currency)
204
205 amount = portfolio.Amount("SC", 10)
206 self.assertEqual(10, abs(amount).value)
207 self.assertEqual("SC", abs(amount).currency)
208
209 def test__add(self):
210 amount1 = portfolio.Amount("XVG", "12.9")
211 amount2 = portfolio.Amount("XVG", "13.1")
212
213 self.assertEqual(26, (amount1 + amount2).value)
214 self.assertEqual("XVG", (amount1 + amount2).currency)
215
216 amount3 = portfolio.Amount("ETH", "1.6")
217 with self.assertRaises(Exception):
218 amount1 + amount3
219
220 amount4 = portfolio.Amount("ETH", 0.0)
221 self.assertEqual(amount1, amount1 + amount4)
222
223 def test__radd(self):
224 amount = portfolio.Amount("XVG", "12.9")
225
226 self.assertEqual(amount, 0 + amount)
227 with self.assertRaises(Exception):
228 4 + amount
229
230 def test__sub(self):
231 amount1 = portfolio.Amount("XVG", "13.3")
232 amount2 = portfolio.Amount("XVG", "13.1")
233
234 self.assertEqual(D("0.2"), (amount1 - amount2).value)
235 self.assertEqual("XVG", (amount1 - amount2).currency)
236
237 amount3 = portfolio.Amount("ETH", "1.6")
238 with self.assertRaises(Exception):
239 amount1 - amount3
240
241 amount4 = portfolio.Amount("ETH", 0.0)
242 self.assertEqual(amount1, amount1 - amount4)
243
244 def test__mul(self):
245 amount = portfolio.Amount("XEM", 11)
246
247 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
248 self.assertEqual(D("33"), (amount * 3).value)
249
250 with self.assertRaises(Exception):
251 amount * amount
252
253 def test__rmul(self):
254 amount = portfolio.Amount("XEM", 11)
255
256 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
257 self.assertEqual(D("33"), (3 * amount).value)
258
259 def test__floordiv(self):
260 amount = portfolio.Amount("XEM", 11)
261
262 self.assertEqual(D("5.5"), (amount / 2).value)
263 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
264
265 with self.assertRaises(Exception):
266 amount / amount
267
268 def test__truediv(self):
269 amount = portfolio.Amount("XEM", 11)
270
271 self.assertEqual(D("5.5"), (amount / 2).value)
272 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
273
274 def test__lt(self):
275 amount1 = portfolio.Amount("BTD", 11.3)
276 amount2 = portfolio.Amount("BTD", 13.1)
277
278 self.assertTrue(amount1 < amount2)
279 self.assertFalse(amount2 < amount1)
280 self.assertFalse(amount1 < amount1)
281
282 amount3 = portfolio.Amount("BTC", 1.6)
283 with self.assertRaises(Exception):
284 amount1 < amount3
285
286 def test__le(self):
287 amount1 = portfolio.Amount("BTD", 11.3)
288 amount2 = portfolio.Amount("BTD", 13.1)
289
290 self.assertTrue(amount1 <= amount2)
291 self.assertFalse(amount2 <= amount1)
292 self.assertTrue(amount1 <= amount1)
293
294 amount3 = portfolio.Amount("BTC", 1.6)
295 with self.assertRaises(Exception):
296 amount1 <= amount3
297
298 def test__gt(self):
299 amount1 = portfolio.Amount("BTD", 11.3)
300 amount2 = portfolio.Amount("BTD", 13.1)
301
302 self.assertTrue(amount2 > amount1)
303 self.assertFalse(amount1 > amount2)
304 self.assertFalse(amount1 > amount1)
305
306 amount3 = portfolio.Amount("BTC", 1.6)
307 with self.assertRaises(Exception):
308 amount3 > amount1
309
310 def test__ge(self):
311 amount1 = portfolio.Amount("BTD", 11.3)
312 amount2 = portfolio.Amount("BTD", 13.1)
313
314 self.assertTrue(amount2 >= amount1)
315 self.assertFalse(amount1 >= amount2)
316 self.assertTrue(amount1 >= amount1)
317
318 amount3 = portfolio.Amount("BTC", 1.6)
319 with self.assertRaises(Exception):
320 amount3 >= amount1
321
322 def test__eq(self):
323 amount1 = portfolio.Amount("BTD", 11.3)
324 amount2 = portfolio.Amount("BTD", 13.1)
325 amount3 = portfolio.Amount("BTD", 11.3)
326
327 self.assertFalse(amount1 == amount2)
328 self.assertFalse(amount2 == amount1)
329 self.assertTrue(amount1 == amount3)
330 self.assertFalse(amount2 == 0)
331
332 amount4 = portfolio.Amount("BTC", 1.6)
333 with self.assertRaises(Exception):
334 amount1 == amount4
335
336 amount5 = portfolio.Amount("BTD", 0)
337 self.assertTrue(amount5 == 0)
338
339 def test__ne(self):
340 amount1 = portfolio.Amount("BTD", 11.3)
341 amount2 = portfolio.Amount("BTD", 13.1)
342 amount3 = portfolio.Amount("BTD", 11.3)
343
344 self.assertTrue(amount1 != amount2)
345 self.assertTrue(amount2 != amount1)
346 self.assertFalse(amount1 != amount3)
347 self.assertTrue(amount2 != 0)
348
349 amount4 = portfolio.Amount("BTC", 1.6)
350 with self.assertRaises(Exception):
351 amount1 != amount4
352
353 amount5 = portfolio.Amount("BTD", 0)
354 self.assertFalse(amount5 != 0)
355
356 def test__neg(self):
357 amount1 = portfolio.Amount("BTD", "11.3")
358
359 self.assertEqual(portfolio.D("-11.3"), (-amount1).value)
360
361 def test__str(self):
362 amount1 = portfolio.Amount("BTX", 32)
363 self.assertEqual("32.00000000 BTX", str(amount1))
364
365 amount2 = portfolio.Amount("USDT", 12000)
366 amount1.linked_to = amount2
367 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
368
369 def test__repr(self):
370 amount1 = portfolio.Amount("BTX", 32)
371 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
372
373 amount2 = portfolio.Amount("USDT", 12000)
374 amount1.linked_to = amount2
375 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
376
377 amount3 = portfolio.Amount("BTC", 0.1)
378 amount2.linked_to = amount3
379 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
380
381 @unittest.skipUnless("unit" in limits, "Unit skipped")
382 class BalanceTest(WebMockTestCase):
383 def test_values(self):
384 balance = portfolio.Balance("BTC", {
385 "exchange_total": "0.65",
386 "exchange_free": "0.35",
387 "exchange_used": "0.30",
388 "margin_total": "-10",
389 "margin_borrowed": "-10",
390 "margin_free": "0",
391 "margin_position_type": "short",
392 "margin_borrowed_base_currency": "USDT",
393 "margin_liquidation_price": "1.20",
394 "margin_pending_gain": "10",
395 "margin_lending_fees": "0.4",
396 "margin_borrowed_base_price": "0.15",
397 })
398 self.assertEqual(portfolio.D("0.65"), balance.exchange_total.value)
399 self.assertEqual(portfolio.D("0.35"), balance.exchange_free.value)
400 self.assertEqual(portfolio.D("0.30"), balance.exchange_used.value)
401 self.assertEqual("BTC", balance.exchange_total.currency)
402 self.assertEqual("BTC", balance.exchange_free.currency)
403 self.assertEqual("BTC", balance.exchange_total.currency)
404
405 self.assertEqual(portfolio.D("-10"), balance.margin_total.value)
406 self.assertEqual(portfolio.D("-10"), balance.margin_borrowed.value)
407 self.assertEqual(portfolio.D("0"), balance.margin_free.value)
408 self.assertEqual("BTC", balance.margin_total.currency)
409 self.assertEqual("BTC", balance.margin_borrowed.currency)
410 self.assertEqual("BTC", balance.margin_free.currency)
411
412 self.assertEqual("BTC", balance.currency)
413
414 self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
415 self.assertEqual("USDT", balance.margin_lending_fees.currency)
416
417 def test__repr(self):
418 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
419 repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
420 balance = portfolio.Balance("BTX", { "exchange_total": 3,
421 "exchange_used": 1, "exchange_free": 2 })
422 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
423
424 balance = portfolio.Balance("BTX", { "exchange_total": 1, "exchange_used": 1})
425 self.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance))
426
427 balance = portfolio.Balance("BTX", { "margin_total": 3,
428 "margin_borrowed": 1, "margin_free": 2 })
429 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance))
430
431 balance = portfolio.Balance("BTX", { "margin_total": 2, "margin_free": 2 })
432 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance))
433
434 balance = portfolio.Balance("BTX", { "margin_total": -3,
435 "margin_borrowed_base_price": D("0.1"),
436 "margin_borrowed_base_currency": "BTC",
437 "margin_lending_fees": D("0.002") })
438 self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
439
440 balance = portfolio.Balance("BTX", { "margin_total": 1,
441 "margin_borrowed": 1, "exchange_free": 2, "exchange_total": 2})
442 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))
443
444 @unittest.skipUnless("unit" in limits, "Unit skipped")
445 class HelperTest(WebMockTestCase):
446 def test_get_ticker(self):
447 market = mock.Mock()
448 market.fetch_ticker.side_effect = [
449 { "bid": 1, "ask": 3 },
450 helper.ExchangeError("foo"),
451 { "bid": 10, "ask": 40 },
452 helper.ExchangeError("foo"),
453 helper.ExchangeError("foo"),
454 ]
455
456 ticker = helper.get_ticker("ETH", "ETC", market)
457 market.fetch_ticker.assert_called_with("ETH/ETC")
458 self.assertEqual(1, ticker["bid"])
459 self.assertEqual(3, ticker["ask"])
460 self.assertEqual(2, ticker["average"])
461 self.assertFalse(ticker["inverted"])
462
463 ticker = helper.get_ticker("ETH", "XVG", market)
464 self.assertEqual(0.0625, ticker["average"])
465 self.assertTrue(ticker["inverted"])
466 self.assertIn("original", ticker)
467 self.assertEqual(10, ticker["original"]["bid"])
468
469 ticker = helper.get_ticker("XVG", "XMR", market)
470 self.assertIsNone(ticker)
471
472 market.fetch_ticker.assert_has_calls([
473 mock.call("ETH/ETC"),
474 mock.call("ETH/XVG"),
475 mock.call("XVG/ETH"),
476 mock.call("XVG/XMR"),
477 mock.call("XMR/XVG"),
478 ])
479
480 market2 = mock.Mock()
481 market2.fetch_ticker.side_effect = [
482 { "bid": 1, "ask": 3 },
483 { "bid": 1.2, "ask": 3.5 },
484 ]
485 ticker1 = helper.get_ticker("ETH", "ETC", market2)
486 ticker2 = helper.get_ticker("ETH", "ETC", market2)
487 ticker3 = helper.get_ticker("ETC", "ETH", market2)
488 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
489 self.assertEqual(1, ticker1["bid"])
490 self.assertDictEqual(ticker1, ticker2)
491 self.assertDictEqual(ticker1, ticker3["original"])
492
493 ticker4 = helper.get_ticker("ETH", "ETC", market2, refresh=True)
494 ticker5 = helper.get_ticker("ETH", "ETC", market2)
495 self.assertEqual(1.2, ticker4["bid"])
496 self.assertDictEqual(ticker4, ticker5)
497
498 market3 = mock.Mock()
499 market3.fetch_ticker.side_effect = [
500 { "bid": 1, "ask": 3 },
501 { "bid": 1.2, "ask": 3.5 },
502 ]
503 ticker6 = helper.get_ticker("ETH", "ETC", market3)
504 helper.ticker_cache_timestamp -= 4
505 ticker7 = helper.get_ticker("ETH", "ETC", market3)
506 helper.ticker_cache_timestamp -= 2
507 ticker8 = helper.get_ticker("ETH", "ETC", market3)
508 self.assertDictEqual(ticker6, ticker7)
509 self.assertEqual(1.2, ticker8["bid"])
510
511 def test_fetch_fees(self):
512 market = mock.Mock()
513 market.fetch_fees.return_value = "Foo"
514 self.assertEqual("Foo", helper.fetch_fees(market))
515 market.fetch_fees.assert_called_once()
516 self.assertEqual("Foo", helper.fetch_fees(market))
517 market.fetch_fees.assert_called_once()
518
519 @mock.patch.object(portfolio.Portfolio, "repartition")
520 @mock.patch.object(helper, "get_ticker")
521 @mock.patch.object(portfolio.TradeStore, "compute_trades")
522 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
523 repartition.return_value = {
524 "XEM": (D("0.75"), "long"),
525 "BTC": (D("0.25"), "long"),
526 }
527 def _get_ticker(c1, c2, market):
528 if c1 == "USDT" and c2 == "BTC":
529 return { "average": D("0.0001") }
530 if c1 == "XVG" and c2 == "BTC":
531 return { "average": D("0.000001") }
532 if c1 == "XEM" and c2 == "BTC":
533 return { "average": D("0.001") }
534 self.fail("Should be called with {}, {}".format(c1, c2))
535 get_ticker.side_effect = _get_ticker
536
537 market = mock.Mock()
538 market.fetch_all_balances.return_value = {
539 "USDT": {
540 "exchange_free": D("10000.0"),
541 "exchange_used": D("0.0"),
542 "exchange_total": D("10000.0"),
543 "total": D("10000.0")
544 },
545 "XVG": {
546 "exchange_free": D("10000.0"),
547 "exchange_used": D("0.0"),
548 "exchange_total": D("10000.0"),
549 "total": D("10000.0")
550 },
551 }
552 helper.prepare_trades(market)
553 compute_trades.assert_called()
554
555 call = compute_trades.call_args
556 self.assertEqual(market, call[1]["market"])
557 self.assertEqual(1, call[0][0]["USDT"].value)
558 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
559 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
560 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
561
562 @mock.patch.object(portfolio.Portfolio, "repartition")
563 @mock.patch.object(helper, "get_ticker")
564 @mock.patch.object(portfolio.TradeStore, "compute_trades")
565 def test_update_trades(self, compute_trades, get_ticker, repartition):
566 repartition.return_value = {
567 "XEM": (D("0.75"), "long"),
568 "BTC": (D("0.25"), "long"),
569 }
570 def _get_ticker(c1, c2, market):
571 if c1 == "USDT" and c2 == "BTC":
572 return { "average": D("0.0001") }
573 if c1 == "XVG" and c2 == "BTC":
574 return { "average": D("0.000001") }
575 if c1 == "XEM" and c2 == "BTC":
576 return { "average": D("0.001") }
577 self.fail("Should be called with {}, {}".format(c1, c2))
578 get_ticker.side_effect = _get_ticker
579
580 market = mock.Mock()
581 market.fetch_all_balances.return_value = {
582 "USDT": {
583 "exchange_free": D("10000.0"),
584 "exchange_used": D("0.0"),
585 "exchange_total": D("10000.0"),
586 "total": D("10000.0")
587 },
588 "XVG": {
589 "exchange_free": D("10000.0"),
590 "exchange_used": D("0.0"),
591 "exchange_total": D("10000.0"),
592 "total": D("10000.0")
593 },
594 }
595 helper.update_trades(market)
596 compute_trades.assert_called()
597
598 call = compute_trades.call_args
599 self.assertEqual(market, call[1]["market"])
600 self.assertEqual(1, call[0][0]["USDT"].value)
601 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
602 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
603 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
604
605 @mock.patch.object(portfolio.Portfolio, "repartition")
606 @mock.patch.object(helper, "get_ticker")
607 @mock.patch.object(portfolio.TradeStore, "compute_trades")
608 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
609 def _get_ticker(c1, c2, market):
610 if c1 == "USDT" and c2 == "BTC":
611 return { "average": D("0.0001") }
612 if c1 == "XVG" and c2 == "BTC":
613 return { "average": D("0.000001") }
614 self.fail("Should be called with {}, {}".format(c1, c2))
615 get_ticker.side_effect = _get_ticker
616
617 market = mock.Mock()
618 market.fetch_all_balances.return_value = {
619 "USDT": {
620 "exchange_free": D("10000.0"),
621 "exchange_used": D("0.0"),
622 "exchange_total": D("10000.0"),
623 "total": D("10000.0")
624 },
625 "XVG": {
626 "exchange_free": D("10000.0"),
627 "exchange_used": D("0.0"),
628 "exchange_total": D("10000.0"),
629 "total": D("10000.0")
630 },
631 }
632 helper.prepare_trades_to_sell_all(market)
633 repartition.assert_not_called()
634 compute_trades.assert_called()
635
636 call = compute_trades.call_args
637 self.assertEqual(market, call[1]["market"])
638 self.assertEqual(1, call[0][0]["USDT"].value)
639 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
640 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
641
642 @mock.patch.object(portfolio.time, "sleep")
643 @mock.patch.object(portfolio.TradeStore, "all_orders")
644 def test_follow_orders(self, all_orders, time_mock):
645 for verbose, debug, sleep in [
646 (True, False, None), (False, False, None),
647 (True, True, None), (True, False, 12),
648 (True, True, 12)]:
649 with self.subTest(sleep=sleep, debug=debug, verbose=verbose), \
650 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
651 portfolio.TradeStore.debug = debug
652 order_mock1 = mock.Mock()
653 order_mock2 = mock.Mock()
654 order_mock3 = mock.Mock()
655 all_orders.side_effect = [
656 [order_mock1, order_mock2],
657 [order_mock1, order_mock2],
658
659 [order_mock1, order_mock3],
660 [order_mock1, order_mock3],
661
662 [order_mock1, order_mock3],
663 [order_mock1, order_mock3],
664
665 []
666 ]
667
668 order_mock1.get_status.side_effect = ["open", "open", "closed"]
669 order_mock2.get_status.side_effect = ["open"]
670 order_mock3.get_status.side_effect = ["open", "closed"]
671
672 order_mock1.trade = mock.Mock()
673 order_mock2.trade = mock.Mock()
674 order_mock3.trade = mock.Mock()
675
676 helper.follow_orders(verbose=verbose, sleep=sleep)
677
678 order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
679 order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
680 self.assertEqual(2, order_mock1.trade.update_order.call_count)
681 self.assertEqual(3, order_mock1.get_status.call_count)
682
683 order_mock2.trade.update_order.assert_any_call(order_mock2, 1)
684 self.assertEqual(1, order_mock2.trade.update_order.call_count)
685 self.assertEqual(1, order_mock2.get_status.call_count)
686
687 order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
688 self.assertEqual(1, order_mock3.trade.update_order.call_count)
689 self.assertEqual(2, order_mock3.get_status.call_count)
690
691 if sleep is None:
692 if debug:
693 time_mock.assert_called_with(7)
694 else:
695 time_mock.assert_called_with(30)
696 else:
697 time_mock.assert_called_with(sleep)
698
699 if verbose:
700 self.assertNotEqual("", stdout_mock.getvalue())
701 else:
702 self.assertEqual("", stdout_mock.getvalue())
703
704 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
705 def test_move_balance(self, fetch_balances):
706 for debug in [True, False]:
707 with self.subTest(debug=debug),\
708 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
709 value_from = portfolio.Amount("BTC", "1.0")
710 value_from.linked_to = portfolio.Amount("ETH", "10.0")
711 value_to = portfolio.Amount("BTC", "10.0")
712 trade1 = portfolio.Trade(value_from, value_to, "ETH")
713
714 value_from = portfolio.Amount("BTC", "0.0")
715 value_from.linked_to = portfolio.Amount("ETH", "0.0")
716 value_to = portfolio.Amount("BTC", "-3.0")
717 trade2 = portfolio.Trade(value_from, value_to, "ETH")
718
719 value_from = portfolio.Amount("USDT", "0.0")
720 value_from.linked_to = portfolio.Amount("XVG", "0.0")
721 value_to = portfolio.Amount("USDT", "-50.0")
722 trade3 = portfolio.Trade(value_from, value_to, "XVG")
723
724 portfolio.TradeStore.all = [trade1, trade2, trade3]
725 balance1 = portfolio.Balance("BTC", { "margin_free": "0" })
726 balance2 = portfolio.Balance("USDT", { "margin_free": "100" })
727 portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2}
728
729 market = mock.Mock()
730
731 helper.move_balances(market, debug=debug)
732
733 fetch_balances.assert_called_with(market)
734 if debug:
735 self.assertRegex(stdout_mock.getvalue(), "market.transfer_balance")
736 else:
737 market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
738 market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
739
740 @mock.patch.object(helper, "prepare_trades")
741 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
742 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
743 @mock.patch('sys.stdout', new_callable=StringIO)
744 def test_print_orders(self, stdout_mock, print_all_with_order, prepare_orders, prepare_trades):
745 market = mock.Mock()
746 portfolio.BalanceStore.all = {
747 "BTC": portfolio.Balance("BTC", {
748 "total": "0.65",
749 "exchange_total":"0.65",
750 "exchange_free": "0.35",
751 "exchange_used": "0.30"}),
752 "ETH": portfolio.Balance("ETH", {
753 "total": 3,
754 "exchange_total": 3,
755 "exchange_free": 3,
756 "exchange_used": 0}),
757 }
758 helper.print_orders(market)
759 prepare_trades.assert_called_with(market, base_currency="BTC",
760 compute_value="average", debug=True)
761 prepare_orders.assert_called_with(compute_value="average")
762 print_all_with_order.assert_called()
763 self.assertRegex(stdout_mock.getvalue(), "Balance")
764
765 @mock.patch.object(helper, "prepare_trades")
766 @mock.patch.object(helper, "follow_orders")
767 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
768 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
769 @mock.patch.object(portfolio.TradeStore, "run_orders")
770 @mock.patch('sys.stdout', new_callable=StringIO)
771 def test_process_sell_needed__1_sell(self, stdout_mock, run_orders,
772 print_all_with_order, prepare_orders, follow_orders,
773 prepare_trades):
774 market = mock.Mock()
775 portfolio.BalanceStore.all = {
776 "BTC": portfolio.Balance("BTC", {
777 "total": "0.65",
778 "exchange_total":"0.65",
779 "exchange_free": "0.35",
780 "exchange_used": "0.30"}),
781 "ETH": portfolio.Balance("ETH", {
782 "total": 3,
783 "exchange_total": 3,
784 "exchange_free": 3,
785 "exchange_used": 0}),
786 }
787 helper.process_sell_needed__1_sell(market)
788 prepare_trades.assert_called_with(market, base_currency="BTC",
789 debug=False)
790 prepare_orders.assert_called_with(compute_value="average",
791 only="dispose")
792 print_all_with_order.assert_called()
793 run_orders.assert_called()
794 follow_orders.assert_called()
795 self.assertRegex(stdout_mock.getvalue(), "Balance")
796
797 @mock.patch.object(helper, "update_trades")
798 @mock.patch.object(helper, "follow_orders")
799 @mock.patch.object(helper, "move_balances")
800 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
801 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
802 @mock.patch.object(portfolio.TradeStore, "run_orders")
803 @mock.patch('sys.stdout', new_callable=StringIO)
804 def test_process_sell_needed__2_buy(self, stdout_mock, run_orders,
805 print_all_with_order, prepare_orders, move_balances,
806 follow_orders, update_trades):
807 market = mock.Mock()
808 portfolio.BalanceStore.all = {
809 "BTC": portfolio.Balance("BTC", {
810 "total": "0.65",
811 "exchange_total":"0.65",
812 "exchange_free": "0.35",
813 "exchange_used": "0.30"}),
814 "ETH": portfolio.Balance("ETH", {
815 "total": 3,
816 "exchange_total": 3,
817 "exchange_free": 3,
818 "exchange_used": 0}),
819 }
820 helper.process_sell_needed__2_buy(market)
821 update_trades.assert_called_with(market, base_currency="BTC",
822 debug=False, only="acquire")
823 prepare_orders.assert_called_with(compute_value="average",
824 only="acquire")
825 print_all_with_order.assert_called()
826 move_balances.assert_called_with(market, debug=False)
827 run_orders.assert_called()
828 follow_orders.assert_called()
829 self.assertRegex(stdout_mock.getvalue(), "Balance")
830
831 @mock.patch.object(helper, "prepare_trades_to_sell_all")
832 @mock.patch.object(helper, "follow_orders")
833 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
834 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
835 @mock.patch.object(portfolio.TradeStore, "run_orders")
836 @mock.patch('sys.stdout', new_callable=StringIO)
837 def test_process_sell_all__1_sell(self, stdout_mock, run_orders,
838 print_all_with_order, prepare_orders, follow_orders,
839 prepare_trades_to_sell_all):
840 market = mock.Mock()
841 portfolio.BalanceStore.all = {
842 "BTC": portfolio.Balance("BTC", {
843 "total": "0.65",
844 "exchange_total":"0.65",
845 "exchange_free": "0.35",
846 "exchange_used": "0.30"}),
847 "ETH": portfolio.Balance("ETH", {
848 "total": 3,
849 "exchange_total": 3,
850 "exchange_free": 3,
851 "exchange_used": 0}),
852 }
853 helper.process_sell_all__1_all_sell(market)
854 prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
855 debug=False)
856 prepare_orders.assert_called_with(compute_value="average")
857 print_all_with_order.assert_called()
858 run_orders.assert_called()
859 follow_orders.assert_called()
860 self.assertRegex(stdout_mock.getvalue(), "Balance")
861
862 @mock.patch.object(helper, "prepare_trades")
863 @mock.patch.object(helper, "follow_orders")
864 @mock.patch.object(helper, "move_balances")
865 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
866 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
867 @mock.patch.object(portfolio.TradeStore, "run_orders")
868 @mock.patch('sys.stdout', new_callable=StringIO)
869 def test_process_sell_all__2_all_buy(self, stdout_mock, run_orders,
870 print_all_with_order, prepare_orders, move_balances,
871 follow_orders, prepare_trades):
872 market = mock.Mock()
873 portfolio.BalanceStore.all = {
874 "BTC": portfolio.Balance("BTC", {
875 "total": "0.65",
876 "exchange_total":"0.65",
877 "exchange_free": "0.35",
878 "exchange_used": "0.30"}),
879 "ETH": portfolio.Balance("ETH", {
880 "total": 3,
881 "exchange_total": 3,
882 "exchange_free": 3,
883 "exchange_used": 0}),
884 }
885 helper.process_sell_all__2_all_buy(market)
886 prepare_trades.assert_called_with(market, base_currency="BTC",
887 debug=False)
888 prepare_orders.assert_called_with()
889 print_all_with_order.assert_called()
890 move_balances.assert_called_with(market, debug=False)
891 run_orders.assert_called()
892 follow_orders.assert_called()
893 self.assertRegex(stdout_mock.getvalue(), "Balance")
894
895
896 @unittest.skipUnless("unit" in limits, "Unit skipped")
897 class TradeStoreTest(WebMockTestCase):
898 @mock.patch.object(portfolio.BalanceStore, "currencies")
899 @mock.patch.object(portfolio.TradeStore, "add_trade_if_matching")
900 def test_compute_trades(self, add_trade_if_matching, currencies):
901 currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
902
903 values_in_base = {
904 "XMR": portfolio.Amount("BTC", D("0.9")),
905 "DASH": portfolio.Amount("BTC", D("0.4")),
906 "XVG": portfolio.Amount("BTC", D("-0.5")),
907 "BTC": portfolio.Amount("BTC", D("0.5")),
908 }
909 new_repartition = {
910 "DASH": portfolio.Amount("BTC", D("0.5")),
911 "XVG": portfolio.Amount("BTC", D("0.1")),
912 "BTC": portfolio.Amount("BTC", D("0.4")),
913 "ETH": portfolio.Amount("BTC", D("0.3")),
914 }
915
916 portfolio.TradeStore.compute_trades(values_in_base,
917 new_repartition, only="only", market="market")
918
919 self.assertEqual(5, add_trade_if_matching.call_count)
920 add_trade_if_matching.assert_any_call(
921 portfolio.Amount("BTC", D("0.9")),
922 portfolio.Amount("BTC", 0),
923 "XMR", only="only", market="market"
924 )
925 add_trade_if_matching.assert_any_call(
926 portfolio.Amount("BTC", D("0.4")),
927 portfolio.Amount("BTC", D("0.5")),
928 "DASH", only="only", market="market"
929 )
930 add_trade_if_matching.assert_any_call(
931 portfolio.Amount("BTC", D("-0.5")),
932 portfolio.Amount("BTC", D("0")),
933 "XVG", only="only", market="market"
934 )
935 add_trade_if_matching.assert_any_call(
936 portfolio.Amount("BTC", D("0")),
937 portfolio.Amount("BTC", D("0.1")),
938 "XVG", only="only", market="market"
939 )
940 add_trade_if_matching.assert_any_call(
941 portfolio.Amount("BTC", D("0")),
942 portfolio.Amount("BTC", D("0.3")),
943 "ETH", only="only", market="market"
944 )
945
946 def test_add_trade_if_matching(self):
947 result = portfolio.TradeStore.add_trade_if_matching(
948 portfolio.Amount("BTC", D("0")),
949 portfolio.Amount("BTC", D("0.3")),
950 "ETH", only="nope", market="market"
951 )
952 self.assertEqual(0, len(portfolio.TradeStore.all))
953 self.assertEqual(False, result)
954
955 portfolio.TradeStore.all = []
956 result = portfolio.TradeStore.add_trade_if_matching(
957 portfolio.Amount("BTC", D("0")),
958 portfolio.Amount("BTC", D("0.3")),
959 "ETH", only=None, market="market"
960 )
961 self.assertEqual(1, len(portfolio.TradeStore.all))
962 self.assertEqual(True, result)
963
964 portfolio.TradeStore.all = []
965 result = portfolio.TradeStore.add_trade_if_matching(
966 portfolio.Amount("BTC", D("0")),
967 portfolio.Amount("BTC", D("0.3")),
968 "ETH", only="acquire", market="market"
969 )
970 self.assertEqual(1, len(portfolio.TradeStore.all))
971 self.assertEqual(True, result)
972
973 portfolio.TradeStore.all = []
974 result = portfolio.TradeStore.add_trade_if_matching(
975 portfolio.Amount("BTC", D("0")),
976 portfolio.Amount("BTC", D("0.3")),
977 "ETH", only="dispose", market="market"
978 )
979 self.assertEqual(0, len(portfolio.TradeStore.all))
980 self.assertEqual(False, result)
981
982 def test_prepare_orders(self):
983 trade_mock1 = mock.Mock()
984 trade_mock2 = mock.Mock()
985
986 portfolio.TradeStore.all.append(trade_mock1)
987 portfolio.TradeStore.all.append(trade_mock2)
988
989 portfolio.TradeStore.prepare_orders()
990 trade_mock1.prepare_order.assert_called_with(compute_value="default")
991 trade_mock2.prepare_order.assert_called_with(compute_value="default")
992
993 portfolio.TradeStore.prepare_orders(compute_value="bla")
994 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
995 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
996
997 trade_mock1.prepare_order.reset_mock()
998 trade_mock2.prepare_order.reset_mock()
999
1000 trade_mock1.action = "foo"
1001 trade_mock2.action = "bar"
1002 portfolio.TradeStore.prepare_orders(only="bar")
1003 trade_mock1.prepare_order.assert_not_called()
1004 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1005
1006 def test_print_all_with_order(self):
1007 trade_mock1 = mock.Mock()
1008 trade_mock2 = mock.Mock()
1009 trade_mock3 = mock.Mock()
1010 portfolio.TradeStore.all = [trade_mock1, trade_mock2, trade_mock3]
1011
1012 portfolio.TradeStore.print_all_with_order()
1013
1014 trade_mock1.print_with_order.assert_called()
1015 trade_mock2.print_with_order.assert_called()
1016 trade_mock3.print_with_order.assert_called()
1017
1018 @mock.patch.object(portfolio.TradeStore, "all_orders")
1019 def test_run_orders(self, all_orders):
1020 order_mock1 = mock.Mock()
1021 order_mock2 = mock.Mock()
1022 order_mock3 = mock.Mock()
1023 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1024 portfolio.TradeStore.run_orders()
1025 all_orders.assert_called_with(state="pending")
1026
1027 order_mock1.run.assert_called()
1028 order_mock2.run.assert_called()
1029 order_mock3.run.assert_called()
1030
1031 def test_all_orders(self):
1032 trade_mock1 = mock.Mock()
1033 trade_mock2 = mock.Mock()
1034
1035 order_mock1 = mock.Mock()
1036 order_mock2 = mock.Mock()
1037 order_mock3 = mock.Mock()
1038
1039 trade_mock1.orders = [order_mock1, order_mock2]
1040 trade_mock2.orders = [order_mock3]
1041
1042 order_mock1.status = "pending"
1043 order_mock2.status = "open"
1044 order_mock3.status = "open"
1045
1046 portfolio.TradeStore.all.append(trade_mock1)
1047 portfolio.TradeStore.all.append(trade_mock2)
1048
1049 orders = portfolio.TradeStore.all_orders()
1050 self.assertEqual(3, len(orders))
1051
1052 open_orders = portfolio.TradeStore.all_orders(state="open")
1053 self.assertEqual(2, len(open_orders))
1054 self.assertEqual([order_mock2, order_mock3], open_orders)
1055
1056 @mock.patch.object(portfolio.TradeStore, "all_orders")
1057 def test_update_all_orders_status(self, all_orders):
1058 order_mock1 = mock.Mock()
1059 order_mock2 = mock.Mock()
1060 order_mock3 = mock.Mock()
1061 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1062 portfolio.TradeStore.update_all_orders_status()
1063 all_orders.assert_called_with(state="open")
1064
1065 order_mock1.get_status.assert_called()
1066 order_mock2.get_status.assert_called()
1067 order_mock3.get_status.assert_called()
1068
1069 @unittest.skipUnless("unit" in limits, "Unit skipped")
1070 class BalanceStoreTest(WebMockTestCase):
1071 def setUp(self):
1072 super(BalanceStoreTest, self).setUp()
1073
1074 self.fetch_balance = {
1075 "ETC": {
1076 "exchange_free": 0,
1077 "exchange_used": 0,
1078 "exchange_total": 0,
1079 "margin_total": 0,
1080 },
1081 "USDT": {
1082 "exchange_free": D("6.0"),
1083 "exchange_used": D("1.2"),
1084 "exchange_total": D("7.2"),
1085 "margin_total": 0,
1086 },
1087 "XVG": {
1088 "exchange_free": 16,
1089 "exchange_used": 0,
1090 "exchange_total": 16,
1091 "margin_total": 0,
1092 },
1093 "XMR": {
1094 "exchange_free": 0,
1095 "exchange_used": 0,
1096 "exchange_total": 0,
1097 "margin_total": D("-1.0"),
1098 "margin_free": 0,
1099 },
1100 }
1101
1102 @mock.patch.object(helper, "get_ticker")
1103 def test_in_currency(self, get_ticker):
1104 portfolio.BalanceStore.all = {
1105 "BTC": portfolio.Balance("BTC", {
1106 "total": "0.65",
1107 "exchange_total":"0.65",
1108 "exchange_free": "0.35",
1109 "exchange_used": "0.30"}),
1110 "ETH": portfolio.Balance("ETH", {
1111 "total": 3,
1112 "exchange_total": 3,
1113 "exchange_free": 3,
1114 "exchange_used": 0}),
1115 }
1116 market = mock.Mock()
1117 get_ticker.return_value = {
1118 "bid": D("0.09"),
1119 "ask": D("0.11"),
1120 "average": D("0.1"),
1121 }
1122
1123 amounts = portfolio.BalanceStore.in_currency("BTC", market)
1124 self.assertEqual("BTC", amounts["ETH"].currency)
1125 self.assertEqual(D("0.65"), amounts["BTC"].value)
1126 self.assertEqual(D("0.30"), amounts["ETH"].value)
1127
1128 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid")
1129 self.assertEqual(D("0.65"), amounts["BTC"].value)
1130 self.assertEqual(D("0.27"), amounts["ETH"].value)
1131
1132 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used")
1133 self.assertEqual(D("0.30"), amounts["BTC"].value)
1134 self.assertEqual(0, amounts["ETH"].value)
1135
1136 def test_fetch_balances(self):
1137 market = mock.Mock()
1138 market.fetch_all_balances.return_value = self.fetch_balance
1139
1140 portfolio.BalanceStore.fetch_balances(market)
1141 self.assertNotIn("ETC", portfolio.BalanceStore.currencies())
1142 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.BalanceStore.currencies()))
1143
1144 portfolio.BalanceStore.all["ETC"] = portfolio.Balance("ETC", {
1145 "exchange_total": "1", "exchange_free": "0",
1146 "exchange_used": "1" })
1147 portfolio.BalanceStore.fetch_balances(market)
1148 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
1149 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))
1150
1151 @mock.patch.object(portfolio.Portfolio, "repartition")
1152 def test_dispatch_assets(self, repartition):
1153 market = mock.Mock()
1154 market.fetch_all_balances.return_value = self.fetch_balance
1155 portfolio.BalanceStore.fetch_balances(market)
1156
1157 self.assertNotIn("XEM", portfolio.BalanceStore.currencies())
1158
1159 repartition.return_value = {
1160 "XEM": (D("0.75"), "long"),
1161 "BTC": (D("0.26"), "long"),
1162 "DASH": (D("0.10"), "short"),
1163 }
1164
1165 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
1166 self.assertIn("XEM", portfolio.BalanceStore.currencies())
1167 self.assertEqual(D("2.6"), amounts["BTC"].value)
1168 self.assertEqual(D("7.5"), amounts["XEM"].value)
1169 self.assertEqual(D("-1.0"), amounts["DASH"].value)
1170
1171 def test_currencies(self):
1172 portfolio.BalanceStore.all = {
1173 "BTC": portfolio.Balance("BTC", {
1174 "total": "0.65",
1175 "exchange_total":"0.65",
1176 "exchange_free": "0.35",
1177 "exchange_used": "0.30"}),
1178 "ETH": portfolio.Balance("ETH", {
1179 "total": 3,
1180 "exchange_total": 3,
1181 "exchange_free": 3,
1182 "exchange_used": 0}),
1183 }
1184 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))
1185
1186 @unittest.skipUnless("unit" in limits, "Unit skipped")
1187 class ComputationTest(WebMockTestCase):
1188 def test_compute_value(self):
1189 compute = mock.Mock()
1190 portfolio.Computation.compute_value("foo", "buy", compute_value=compute)
1191 compute.assert_called_with("foo", "ask")
1192
1193 compute.reset_mock()
1194 portfolio.Computation.compute_value("foo", "sell", compute_value=compute)
1195 compute.assert_called_with("foo", "bid")
1196
1197 compute.reset_mock()
1198 portfolio.Computation.compute_value("foo", "ask", compute_value=compute)
1199 compute.assert_called_with("foo", "ask")
1200
1201 compute.reset_mock()
1202 portfolio.Computation.compute_value("foo", "bid", compute_value=compute)
1203 compute.assert_called_with("foo", "bid")
1204
1205 compute.reset_mock()
1206 portfolio.Computation.computations["test"] = compute
1207 portfolio.Computation.compute_value("foo", "bid", compute_value="test")
1208 compute.assert_called_with("foo", "bid")
1209
1210
1211 @unittest.skipUnless("unit" in limits, "Unit skipped")
1212 class TradeTest(WebMockTestCase):
1213
1214 def test_values_assertion(self):
1215 value_from = portfolio.Amount("BTC", "1.0")
1216 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1217 value_to = portfolio.Amount("BTC", "1.0")
1218 trade = portfolio.Trade(value_from, value_to, "ETH")
1219 self.assertEqual("BTC", trade.base_currency)
1220 self.assertEqual("ETH", trade.currency)
1221
1222 with self.assertRaises(AssertionError):
1223 portfolio.Trade(value_from, value_to, "ETC")
1224 with self.assertRaises(AssertionError):
1225 value_from.linked_to = None
1226 portfolio.Trade(value_from, value_to, "ETH")
1227 with self.assertRaises(AssertionError):
1228 value_from.currency = "ETH"
1229 portfolio.Trade(value_from, value_to, "ETH")
1230
1231 value_from = portfolio.Amount("BTC", 0)
1232 trade = portfolio.Trade(value_from, value_to, "ETH")
1233 self.assertEqual(0, trade.value_from.linked_to)
1234
1235 def test_action(self):
1236 value_from = portfolio.Amount("BTC", "1.0")
1237 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1238 value_to = portfolio.Amount("BTC", "1.0")
1239 trade = portfolio.Trade(value_from, value_to, "ETH")
1240
1241 self.assertIsNone(trade.action)
1242
1243 value_from = portfolio.Amount("BTC", "1.0")
1244 value_from.linked_to = portfolio.Amount("BTC", "1.0")
1245 value_to = portfolio.Amount("BTC", "2.0")
1246 trade = portfolio.Trade(value_from, value_to, "BTC")
1247
1248 self.assertIsNone(trade.action)
1249
1250 value_from = portfolio.Amount("BTC", "0.5")
1251 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1252 value_to = portfolio.Amount("BTC", "1.0")
1253 trade = portfolio.Trade(value_from, value_to, "ETH")
1254
1255 self.assertEqual("acquire", trade.action)
1256
1257 value_from = portfolio.Amount("BTC", "0")
1258 value_from.linked_to = portfolio.Amount("ETH", "0")
1259 value_to = portfolio.Amount("BTC", "-1.0")
1260 trade = portfolio.Trade(value_from, value_to, "ETH")
1261
1262 self.assertEqual("acquire", trade.action)
1263
1264 def test_order_action(self):
1265 value_from = portfolio.Amount("BTC", "0.5")
1266 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1267 value_to = portfolio.Amount("BTC", "1.0")
1268 trade = portfolio.Trade(value_from, value_to, "ETH")
1269
1270 self.assertEqual("buy", trade.order_action(False))
1271 self.assertEqual("sell", trade.order_action(True))
1272
1273 value_from = portfolio.Amount("BTC", "0")
1274 value_from.linked_to = portfolio.Amount("ETH", "0")
1275 value_to = portfolio.Amount("BTC", "-1.0")
1276 trade = portfolio.Trade(value_from, value_to, "ETH")
1277
1278 self.assertEqual("sell", trade.order_action(False))
1279 self.assertEqual("buy", trade.order_action(True))
1280
1281 def test_trade_type(self):
1282 value_from = portfolio.Amount("BTC", "0.5")
1283 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1284 value_to = portfolio.Amount("BTC", "1.0")
1285 trade = portfolio.Trade(value_from, value_to, "ETH")
1286
1287 self.assertEqual("long", trade.trade_type)
1288
1289 value_from = portfolio.Amount("BTC", "0")
1290 value_from.linked_to = portfolio.Amount("ETH", "0")
1291 value_to = portfolio.Amount("BTC", "-1.0")
1292 trade = portfolio.Trade(value_from, value_to, "ETH")
1293
1294 self.assertEqual("short", trade.trade_type)
1295
1296 def test_filled_amount(self):
1297 value_from = portfolio.Amount("BTC", "0.5")
1298 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1299 value_to = portfolio.Amount("BTC", "1.0")
1300 trade = portfolio.Trade(value_from, value_to, "ETH")
1301
1302 order1 = mock.Mock()
1303 order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")
1304
1305 order2 = mock.Mock()
1306 order2.filled_amount.return_value = portfolio.Amount("ETH", "0.01")
1307 trade.orders.append(order1)
1308 trade.orders.append(order2)
1309
1310 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount())
1311 order1.filled_amount.assert_called_with(in_base_currency=False)
1312 order2.filled_amount.assert_called_with(in_base_currency=False)
1313
1314 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
1315 order1.filled_amount.assert_called_with(in_base_currency=False)
1316 order2.filled_amount.assert_called_with(in_base_currency=False)
1317
1318 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
1319 order1.filled_amount.assert_called_with(in_base_currency=True)
1320 order2.filled_amount.assert_called_with(in_base_currency=True)
1321
1322 @mock.patch.object(helper, "get_ticker")
1323 @mock.patch.object(portfolio.Computation, "compute_value")
1324 @mock.patch.object(portfolio.Trade, "filled_amount")
1325 @mock.patch.object(portfolio, "Order")
1326 def test_prepare_order(self, Order, filled_amount, compute_value, get_ticker):
1327 Order.return_value = "Order"
1328
1329 with self.subTest(desc="Nothing to do"):
1330 value_from = portfolio.Amount("BTC", "10")
1331 value_from.rate = D("0.1")
1332 value_from.linked_to = portfolio.Amount("FOO", "100")
1333 value_to = portfolio.Amount("BTC", "10")
1334 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1335
1336 trade.prepare_order()
1337
1338 filled_amount.assert_not_called()
1339 compute_value.assert_not_called()
1340 self.assertEqual(0, len(trade.orders))
1341 Order.assert_not_called()
1342
1343 get_ticker.return_value = { "inverted": False }
1344 with self.subTest(desc="Already filled"), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1345 filled_amount.return_value = portfolio.Amount("FOO", "100")
1346 compute_value.return_value = D("0.125")
1347
1348 value_from = portfolio.Amount("BTC", "10")
1349 value_from.rate = D("0.1")
1350 value_from.linked_to = portfolio.Amount("FOO", "100")
1351 value_to = portfolio.Amount("BTC", "0")
1352 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1353
1354 trade.prepare_order()
1355
1356 filled_amount.assert_called_with(in_base_currency=False)
1357 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1358 self.assertEqual(0, len(trade.orders))
1359 self.assertRegex(stdout_mock.getvalue(), "Less to do than already filled: ")
1360 Order.assert_not_called()
1361
1362 with self.subTest(action="dispose", inverted=False):
1363 filled_amount.return_value = portfolio.Amount("FOO", "60")
1364 compute_value.return_value = D("0.125")
1365
1366 value_from = portfolio.Amount("BTC", "10")
1367 value_from.rate = D("0.1")
1368 value_from.linked_to = portfolio.Amount("FOO", "100")
1369 value_to = portfolio.Amount("BTC", "1")
1370 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1371
1372 trade.prepare_order()
1373
1374 filled_amount.assert_called_with(in_base_currency=False)
1375 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1376 self.assertEqual(1, len(trade.orders))
1377 Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
1378 D("0.125"), "BTC", "long", "market",
1379 trade, close_if_possible=False)
1380
1381 with self.subTest(action="acquire", inverted=False):
1382 filled_amount.return_value = portfolio.Amount("BTC", "3")
1383 compute_value.return_value = D("0.125")
1384
1385 value_from = portfolio.Amount("BTC", "1")
1386 value_from.rate = D("0.1")
1387 value_from.linked_to = portfolio.Amount("FOO", "10")
1388 value_to = portfolio.Amount("BTC", "10")
1389 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1390
1391 trade.prepare_order()
1392
1393 filled_amount.assert_called_with(in_base_currency=True)
1394 compute_value.assert_called_with(get_ticker.return_value, "buy", compute_value="default")
1395 self.assertEqual(1, len(trade.orders))
1396
1397 Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
1398 D("0.125"), "BTC", "long", "market",
1399 trade, close_if_possible=False)
1400
1401 with self.subTest(close_if_possible=True):
1402 filled_amount.return_value = portfolio.Amount("FOO", "0")
1403 compute_value.return_value = D("0.125")
1404
1405 value_from = portfolio.Amount("BTC", "10")
1406 value_from.rate = D("0.1")
1407 value_from.linked_to = portfolio.Amount("FOO", "100")
1408 value_to = portfolio.Amount("BTC", "0")
1409 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1410
1411 trade.prepare_order()
1412
1413 filled_amount.assert_called_with(in_base_currency=False)
1414 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1415 self.assertEqual(1, len(trade.orders))
1416 Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
1417 D("0.125"), "BTC", "long", "market",
1418 trade, close_if_possible=True)
1419
1420 get_ticker.return_value = { "inverted": True, "original": {} }
1421 with self.subTest(action="dispose", inverted=True):
1422 filled_amount.return_value = portfolio.Amount("FOO", "300")
1423 compute_value.return_value = D("125")
1424
1425 value_from = portfolio.Amount("BTC", "10")
1426 value_from.rate = D("0.01")
1427 value_from.linked_to = portfolio.Amount("FOO", "1000")
1428 value_to = portfolio.Amount("BTC", "1")
1429 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1430
1431 trade.prepare_order(compute_value="foo")
1432
1433 filled_amount.assert_called_with(in_base_currency=True)
1434 compute_value.assert_called_with(get_ticker.return_value["original"], "buy", compute_value="foo")
1435 self.assertEqual(1, len(trade.orders))
1436 Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
1437 D("125"), "FOO", "long", "market",
1438 trade, close_if_possible=False)
1439
1440 with self.subTest(action="acquire", inverted=True):
1441 filled_amount.return_value = portfolio.Amount("BTC", "4")
1442 compute_value.return_value = D("125")
1443
1444 value_from = portfolio.Amount("BTC", "1")
1445 value_from.rate = D("0.01")
1446 value_from.linked_to = portfolio.Amount("FOO", "100")
1447 value_to = portfolio.Amount("BTC", "10")
1448 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1449
1450 trade.prepare_order(compute_value="foo")
1451
1452 filled_amount.assert_called_with(in_base_currency=False)
1453 compute_value.assert_called_with(get_ticker.return_value["original"], "sell", compute_value="foo")
1454 self.assertEqual(1, len(trade.orders))
1455 Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
1456 D("125"), "FOO", "long", "market",
1457 trade, close_if_possible=False)
1458
1459
1460 @mock.patch.object(portfolio.Trade, "prepare_order")
1461 def test_update_order(self, prepare_order):
1462 order_mock = mock.Mock()
1463 new_order_mock = mock.Mock()
1464
1465 value_from = portfolio.Amount("BTC", "0.5")
1466 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1467 value_to = portfolio.Amount("BTC", "1.0")
1468 trade = portfolio.Trade(value_from, value_to, "ETH")
1469 prepare_order.return_value = new_order_mock
1470
1471 for i in [0, 1, 3, 4, 6]:
1472 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1473 trade.update_order(order_mock, i)
1474 order_mock.cancel.assert_not_called()
1475 new_order_mock.run.assert_not_called()
1476 self.assertRegex(stdout_mock.getvalue(), "tick {}, waiting".format(i))
1477
1478 order_mock.reset_mock()
1479 new_order_mock.reset_mock()
1480 trade.orders = []
1481
1482 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1483 trade.update_order(order_mock, 2)
1484 order_mock.cancel.assert_called()
1485 new_order_mock.run.assert_called()
1486 prepare_order.assert_called()
1487 self.assertRegex(stdout_mock.getvalue(), "tick 2, cancelling and adjusting")
1488
1489 order_mock.reset_mock()
1490 new_order_mock.reset_mock()
1491 trade.orders = []
1492
1493 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1494 trade.update_order(order_mock, 5)
1495 order_mock.cancel.assert_called()
1496 new_order_mock.run.assert_called()
1497 prepare_order.assert_called()
1498 self.assertRegex(stdout_mock.getvalue(), "tick 5, cancelling and adjusting")
1499
1500 order_mock.reset_mock()
1501 new_order_mock.reset_mock()
1502 trade.orders = []
1503
1504 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1505 trade.update_order(order_mock, 7)
1506 order_mock.cancel.assert_called()
1507 new_order_mock.run.assert_called()
1508 prepare_order.assert_called_with(compute_value="default")
1509 self.assertRegex(stdout_mock.getvalue(), "tick 7, fallbacking to market value")
1510 self.assertRegex(stdout_mock.getvalue(), "tick 7, market value, cancelling and adjusting to")
1511
1512 order_mock.reset_mock()
1513 new_order_mock.reset_mock()
1514 trade.orders = []
1515
1516 for i in [10, 13, 16]:
1517 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1518 trade.update_order(order_mock, i)
1519 order_mock.cancel.assert_called()
1520 new_order_mock.run.assert_called()
1521 prepare_order.assert_called_with(compute_value="default")
1522 self.assertNotRegex(stdout_mock.getvalue(), "tick {}, fallbacking to market value".format(i))
1523 self.assertRegex(stdout_mock.getvalue(), "tick {}, market value, cancelling and adjusting to".format(i))
1524
1525 order_mock.reset_mock()
1526 new_order_mock.reset_mock()
1527 trade.orders = []
1528
1529 for i in [8, 9, 11, 12]:
1530 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1531 trade.update_order(order_mock, i)
1532 order_mock.cancel.assert_not_called()
1533 new_order_mock.run.assert_not_called()
1534 self.assertEqual("", stdout_mock.getvalue())
1535
1536 order_mock.reset_mock()
1537 new_order_mock.reset_mock()
1538 trade.orders = []
1539
1540
1541 @mock.patch('sys.stdout', new_callable=StringIO)
1542 def test_print_with_order(self, mock_stdout):
1543 value_from = portfolio.Amount("BTC", "0.5")
1544 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1545 value_to = portfolio.Amount("BTC", "1.0")
1546 trade = portfolio.Trade(value_from, value_to, "ETH")
1547
1548 order_mock1 = mock.Mock()
1549 order_mock1.__repr__ = mock.Mock()
1550 order_mock1.__repr__.return_value = "Mock 1"
1551 order_mock2 = mock.Mock()
1552 order_mock2.__repr__ = mock.Mock()
1553 order_mock2.__repr__.return_value = "Mock 2"
1554 trade.orders.append(order_mock1)
1555 trade.orders.append(order_mock2)
1556
1557 trade.print_with_order()
1558
1559 out = mock_stdout.getvalue().split("\n")
1560 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0])
1561 self.assertEqual("\tMock 1", out[1])
1562 self.assertEqual("\tMock 2", out[2])
1563
1564 def test__repr(self):
1565 value_from = portfolio.Amount("BTC", "0.5")
1566 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1567 value_to = portfolio.Amount("BTC", "1.0")
1568 trade = portfolio.Trade(value_from, value_to, "ETH")
1569
1570 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
1571
1572 @unittest.skipUnless("unit" in limits, "Unit skipped")
1573 class OrderTest(WebMockTestCase):
1574 def test_values(self):
1575 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1576 D("0.1"), "BTC", "long", "market", "trade")
1577 self.assertEqual("buy", order.action)
1578 self.assertEqual(10, order.amount.value)
1579 self.assertEqual("ETH", order.amount.currency)
1580 self.assertEqual(D("0.1"), order.rate)
1581 self.assertEqual("BTC", order.base_currency)
1582 self.assertEqual("market", order.market)
1583 self.assertEqual("long", order.trade_type)
1584 self.assertEqual("pending", order.status)
1585 self.assertEqual("trade", order.trade)
1586 self.assertIsNone(order.id)
1587 self.assertFalse(order.close_if_possible)
1588
1589 def test__repr(self):
1590 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1591 D("0.1"), "BTC", "long", "market", "trade")
1592 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order))
1593
1594 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1595 D("0.1"), "BTC", "long", "market", "trade",
1596 close_if_possible=True)
1597 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order))
1598
1599 def test_account(self):
1600 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1601 D("0.1"), "BTC", "long", "market", "trade")
1602 self.assertEqual("exchange", order.account)
1603
1604 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
1605 D("0.1"), "BTC", "short", "market", "trade")
1606 self.assertEqual("margin", order.account)
1607
1608 def test_pending(self):
1609 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1610 D("0.1"), "BTC", "long", "market", "trade")
1611 self.assertTrue(order.pending)
1612 order.status = "open"
1613 self.assertFalse(order.pending)
1614
1615 def test_open(self):
1616 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1617 D("0.1"), "BTC", "long", "market", "trade")
1618 self.assertFalse(order.open)
1619 order.status = "open"
1620 self.assertTrue(order.open)
1621
1622 def test_finished(self):
1623 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1624 D("0.1"), "BTC", "long", "market", "trade")
1625 self.assertFalse(order.finished)
1626 order.status = "closed"
1627 self.assertTrue(order.finished)
1628 order.status = "canceled"
1629 self.assertTrue(order.finished)
1630 order.status = "error"
1631 self.assertTrue(order.finished)
1632
1633 @mock.patch.object(portfolio.Order, "fetch")
1634 def test_cancel(self, fetch):
1635 market = mock.Mock()
1636 portfolio.TradeStore.debug = True
1637 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1638 D("0.1"), "BTC", "long", market, "trade")
1639 order.status = "open"
1640
1641 order.cancel()
1642 market.cancel_order.assert_not_called()
1643 self.assertEqual("canceled", order.status)
1644
1645 portfolio.TradeStore.debug = False
1646 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1647 D("0.1"), "BTC", "long", market, "trade")
1648 order.status = "open"
1649 order.id = 42
1650
1651 order.cancel()
1652 market.cancel_order.assert_called_with(42)
1653 fetch.assert_called_once()
1654
1655 def test_dust_amount_remaining(self):
1656 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1657 D("0.1"), "BTC", "long", "market", "trade")
1658 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
1659 self.assertFalse(order.dust_amount_remaining())
1660
1661 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001")))
1662 self.assertTrue(order.dust_amount_remaining())
1663
1664 @mock.patch.object(portfolio.Order, "fetch")
1665 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
1666 def test_remaining_amount(self, filled_amount, fetch):
1667 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1668 D("0.1"), "BTC", "long", "market", "trade")
1669
1670 self.assertEqual(9, order.remaining_amount().value)
1671 order.fetch.assert_not_called()
1672
1673 order.status = "open"
1674 self.assertEqual(9, order.remaining_amount().value)
1675 fetch.assert_called_once()
1676
1677 @mock.patch.object(portfolio.Order, "fetch")
1678 def test_filled_amount(self, fetch):
1679 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1680 D("0.1"), "BTC", "long", "market", "trade")
1681 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
1682 "tradeID": 42, "type": "buy", "fee": "0.0015",
1683 "date": "2017-12-30 12:00:12", "rate": "0.1",
1684 "amount": "3", "total": "0.3"
1685 }))
1686 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
1687 "tradeID": 43, "type": "buy", "fee": "0.0015",
1688 "date": "2017-12-30 13:00:12", "rate": "0.2",
1689 "amount": "2", "total": "0.4"
1690 }))
1691 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount())
1692 fetch.assert_not_called()
1693 order.status = "open"
1694 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False))
1695 fetch.assert_called_once()
1696 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))
1697
1698 def test_fetch_mouvements(self):
1699 market = mock.Mock()
1700 market.privatePostReturnOrderTrades.return_value = [
1701 {
1702 "tradeID": 42, "type": "buy", "fee": "0.0015",
1703 "date": "2017-12-30 12:00:12", "rate": "0.1",
1704 "amount": "3", "total": "0.3"
1705 },
1706 {
1707 "tradeID": 43, "type": "buy", "fee": "0.0015",
1708 "date": "2017-12-30 13:00:12", "rate": "0.2",
1709 "amount": "2", "total": "0.4"
1710 }
1711 ]
1712 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1713 D("0.1"), "BTC", "long", market, "trade")
1714 order.id = 12
1715 order.mouvements = ["Foo", "Bar", "Baz"]
1716
1717 order.fetch_mouvements()
1718
1719 market.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
1720 self.assertEqual(2, len(order.mouvements))
1721 self.assertEqual(42, order.mouvements[0].id)
1722 self.assertEqual(43, order.mouvements[1].id)
1723
1724 market.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
1725 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1726 D("0.1"), "BTC", "long", market, "trade")
1727 order.fetch_mouvements()
1728 self.assertEqual(0, len(order.mouvements))
1729
1730 def test_mark_finished_order(self):
1731 market = mock.Mock()
1732 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1733 D("0.1"), "BTC", "short", market, "trade",
1734 close_if_possible=True)
1735 order.status = "closed"
1736 portfolio.TradeStore.debug = False
1737
1738 order.mark_finished_order()
1739 market.close_margin_position.assert_called_with("ETH", "BTC")
1740 market.close_margin_position.reset_mock()
1741
1742 order.status = "open"
1743 order.mark_finished_order()
1744 market.close_margin_position.assert_not_called()
1745
1746 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1747 D("0.1"), "BTC", "short", market, "trade",
1748 close_if_possible=False)
1749 order.status = "closed"
1750 order.mark_finished_order()
1751 market.close_margin_position.assert_not_called()
1752
1753 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
1754 D("0.1"), "BTC", "short", market, "trade",
1755 close_if_possible=True)
1756 order.status = "closed"
1757 order.mark_finished_order()
1758 market.close_margin_position.assert_not_called()
1759
1760 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1761 D("0.1"), "BTC", "long", market, "trade",
1762 close_if_possible=True)
1763 order.status = "closed"
1764 order.mark_finished_order()
1765 market.close_margin_position.assert_not_called()
1766
1767 portfolio.TradeStore.debug = True
1768
1769 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1770 D("0.1"), "BTC", "short", market, "trade",
1771 close_if_possible=True)
1772 order.status = "closed"
1773
1774 order.mark_finished_order()
1775 market.close_margin_position.assert_not_called()
1776
1777 @mock.patch.object(portfolio.Order, "fetch_mouvements")
1778 def test_fetch(self, fetch_mouvements):
1779 time = self.time.time()
1780 with mock.patch.object(portfolio.time, "time") as time_mock:
1781 market = mock.Mock()
1782 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1783 D("0.1"), "BTC", "long", market, "trade")
1784 order.id = 45
1785 with self.subTest(debug=True):
1786 portfolio.TradeStore.debug = True
1787 order.fetch()
1788 time_mock.assert_not_called()
1789 order.fetch(force=True)
1790 time_mock.assert_not_called()
1791 market.fetch_order.assert_not_called()
1792 fetch_mouvements.assert_not_called()
1793 self.assertIsNone(order.fetch_cache_timestamp)
1794
1795 with self.subTest(debug=False):
1796 portfolio.TradeStore.debug = False
1797 time_mock.return_value = time
1798 market.fetch_order.return_value = {
1799 "status": "foo",
1800 "datetime": "timestamp"
1801 }
1802 order.fetch()
1803
1804 market.fetch_order.assert_called_once()
1805 fetch_mouvements.assert_called_once()
1806 self.assertEqual("foo", order.status)
1807 self.assertEqual("timestamp", order.timestamp)
1808 self.assertEqual(time, order.fetch_cache_timestamp)
1809 self.assertEqual(1, len(order.results))
1810
1811 market.fetch_order.reset_mock()
1812 fetch_mouvements.reset_mock()
1813
1814 time_mock.return_value = time + 8
1815 order.fetch()
1816 market.fetch_order.assert_not_called()
1817 fetch_mouvements.assert_not_called()
1818
1819 order.fetch(force=True)
1820 market.fetch_order.assert_called_once()
1821 fetch_mouvements.assert_called_once()
1822
1823 market.fetch_order.reset_mock()
1824 fetch_mouvements.reset_mock()
1825
1826 time_mock.return_value = time + 19
1827 order.fetch()
1828 market.fetch_order.assert_called_once()
1829 fetch_mouvements.assert_called_once()
1830
1831 @mock.patch.object(portfolio.Order, "fetch")
1832 @mock.patch.object(portfolio.Order, "mark_finished_order")
1833 def test_get_status(self, mark_finished_order, fetch):
1834 with self.subTest(debug=True):
1835 portfolio.TradeStore.debug = True
1836 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1837 D("0.1"), "BTC", "long", "market", "trade")
1838 self.assertEqual("pending", order.get_status())
1839 fetch.assert_not_called()
1840
1841 with self.subTest(debug=False, finished=False):
1842 portfolio.TradeStore.debug = False
1843 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1844 D("0.1"), "BTC", "long", "market", "trade")
1845 def _fetch(order):
1846 def update_status():
1847 order.status = "open"
1848 return update_status
1849 fetch.side_effect = _fetch(order)
1850 self.assertEqual("open", order.get_status())
1851 mark_finished_order.assert_not_called()
1852 fetch.assert_called_once()
1853
1854 mark_finished_order.reset_mock()
1855 fetch.reset_mock()
1856 with self.subTest(debug=False, finished=True):
1857 portfolio.TradeStore.debug = False
1858 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1859 D("0.1"), "BTC", "long", "market", "trade")
1860 def _fetch(order):
1861 def update_status():
1862 order.status = "closed"
1863 return update_status
1864 fetch.side_effect = _fetch(order)
1865 self.assertEqual("closed", order.get_status())
1866 mark_finished_order.assert_called_once()
1867 fetch.assert_called_once()
1868
1869 def test_run(self):
1870 market = mock.Mock()
1871
1872 market.order_precision.return_value = 4
1873 with self.subTest(debug=True),\
1874 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1875 portfolio.TradeStore.debug = True
1876 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1877 D("0.1"), "BTC", "long", market, "trade")
1878 order.run()
1879 market.create_order.assert_not_called()
1880 self.assertEqual("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)\n", stdout_mock.getvalue())
1881 self.assertEqual("open", order.status)
1882 self.assertEqual(1, len(order.results))
1883 self.assertEqual(-1, order.id)
1884
1885 market.create_order.reset_mock()
1886 with self.subTest(debug=False):
1887 portfolio.TradeStore.debug = False
1888 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1889 D("0.1"), "BTC", "long", market, "trade")
1890 market.create_order.return_value = { "id": 123 }
1891 order.run()
1892 market.create_order.assert_called_once()
1893 self.assertEqual(1, len(order.results))
1894 self.assertEqual("open", order.status)
1895
1896 market.create_order.reset_mock()
1897 with self.subTest(exception=True),\
1898 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1899 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1900 D("0.1"), "BTC", "long", market, "trade")
1901 market.create_order.side_effect = Exception("bouh")
1902 order.run()
1903 market.create_order.assert_called_once()
1904 self.assertEqual(0, len(order.results))
1905 self.assertEqual("error", order.status)
1906 self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order")
1907 self.assertRegex(stdout_mock.getvalue(), "Exception: bouh")
1908
1909 market.create_order.reset_mock()
1910 with self.subTest(dust_amount_exception=True),\
1911 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1912 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
1913 D("0.1"), "BTC", "long", market, "trade")
1914 market.create_order.side_effect = portfolio.ExchangeNotAvailable
1915 order.run()
1916 market.create_order.assert_called_once()
1917 self.assertEqual(0, len(order.results))
1918 self.assertEqual("closed", order.status)
1919 mark_finished_order.assert_called_once()
1920
1921
1922 @unittest.skipUnless("unit" in limits, "Unit skipped")
1923 class MouvementTest(WebMockTestCase):
1924 def test_values(self):
1925 mouvement = portfolio.Mouvement("ETH", "BTC", {
1926 "tradeID": 42, "type": "buy", "fee": "0.0015",
1927 "date": "2017-12-30 12:00:12", "rate": "0.1",
1928 "amount": "10", "total": "1"
1929 })
1930 self.assertEqual("ETH", mouvement.currency)
1931 self.assertEqual("BTC", mouvement.base_currency)
1932 self.assertEqual(42, mouvement.id)
1933 self.assertEqual("buy", mouvement.action)
1934 self.assertEqual(D("0.0015"), mouvement.fee_rate)
1935 self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), mouvement.date)
1936 self.assertEqual(D("0.1"), mouvement.rate)
1937 self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
1938 self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)
1939
1940 mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
1941 self.assertIsNone(mouvement.date)
1942 self.assertIsNone(mouvement.id)
1943 self.assertIsNone(mouvement.action)
1944 self.assertEqual(-1, mouvement.fee_rate)
1945 self.assertEqual(0, mouvement.rate)
1946 self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
1947 self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)
1948
1949 @unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
1950 class AcceptanceTest(WebMockTestCase):
1951 @unittest.expectedFailure
1952 def test_success_sell_only_necessary(self):
1953 fetch_balance = {
1954 "ETH": {
1955 "exchange_free": D("1.0"),
1956 "exchange_used": D("0.0"),
1957 "exchange_total": D("1.0"),
1958 "total": D("1.0"),
1959 },
1960 "ETC": {
1961 "exchange_free": D("4.0"),
1962 "exchange_used": D("0.0"),
1963 "exchange_total": D("4.0"),
1964 "total": D("4.0"),
1965 },
1966 "XVG": {
1967 "exchange_free": D("1000.0"),
1968 "exchange_used": D("0.0"),
1969 "exchange_total": D("1000.0"),
1970 "total": D("1000.0"),
1971 },
1972 }
1973 repartition = {
1974 "ETH": (D("0.25"), "long"),
1975 "ETC": (D("0.25"), "long"),
1976 "BTC": (D("0.4"), "long"),
1977 "BTD": (D("0.01"), "short"),
1978 "B2X": (D("0.04"), "long"),
1979 "USDT": (D("0.05"), "long"),
1980 }
1981
1982 def fetch_ticker(symbol):
1983 if symbol == "ETH/BTC":
1984 return {
1985 "symbol": "ETH/BTC",
1986 "bid": D("0.14"),
1987 "ask": D("0.16")
1988 }
1989 if symbol == "ETC/BTC":
1990 return {
1991 "symbol": "ETC/BTC",
1992 "bid": D("0.002"),
1993 "ask": D("0.003")
1994 }
1995 if symbol == "XVG/BTC":
1996 return {
1997 "symbol": "XVG/BTC",
1998 "bid": D("0.00003"),
1999 "ask": D("0.00005")
2000 }
2001 if symbol == "BTD/BTC":
2002 return {
2003 "symbol": "BTD/BTC",
2004 "bid": D("0.0008"),
2005 "ask": D("0.0012")
2006 }
2007 if symbol == "B2X/BTC":
2008 return {
2009 "symbol": "B2X/BTC",
2010 "bid": D("0.0008"),
2011 "ask": D("0.0012")
2012 }
2013 if symbol == "USDT/BTC":
2014 raise helper.ExchangeError
2015 if symbol == "BTC/USDT":
2016 return {
2017 "symbol": "BTC/USDT",
2018 "bid": D("14000"),
2019 "ask": D("16000")
2020 }
2021 self.fail("Shouldn't have been called with {}".format(symbol))
2022
2023 market = mock.Mock()
2024 market.fetch_all_balances.return_value = fetch_balance
2025 market.fetch_ticker.side_effect = fetch_ticker
2026 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
2027 # Action 1
2028 helper.prepare_trades(market)
2029
2030 balances = portfolio.BalanceStore.all
2031 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
2032 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
2033 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
2034
2035
2036 trades = portfolio.TradeStore.all
2037 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
2038 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
2039 self.assertEqual("dispose", trades[0].action)
2040
2041 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
2042 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
2043 self.assertEqual("acquire", trades[1].action)
2044
2045 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
2046 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
2047 self.assertEqual("dispose", trades[2].action)
2048
2049 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
2050 self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
2051 self.assertEqual("acquire", trades[3].action)
2052
2053 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
2054 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
2055 self.assertEqual("acquire", trades[4].action)
2056
2057 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
2058 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
2059 self.assertEqual("acquire", trades[5].action)
2060
2061 # Action 2
2062 portfolio.TradeStore.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001"))
2063
2064 all_orders = portfolio.TradeStore.all_orders(state="pending")
2065 self.assertEqual(2, len(all_orders))
2066 self.assertEqual(2, 3*all_orders[0].amount.value)
2067 self.assertEqual(D("0.14014"), all_orders[0].rate)
2068 self.assertEqual(1000, all_orders[1].amount.value)
2069 self.assertEqual(D("0.00003003"), all_orders[1].rate)
2070
2071
2072 def create_order(symbol, type, action, amount, price=None, account="exchange"):
2073 self.assertEqual("limit", type)
2074 if symbol == "ETH/BTC":
2075 self.assertEqual("sell", action)
2076 self.assertEqual(D('0.66666666'), amount)
2077 self.assertEqual(D("0.14014"), price)
2078 elif symbol == "XVG/BTC":
2079 self.assertEqual("sell", action)
2080 self.assertEqual(1000, amount)
2081 self.assertEqual(D("0.00003003"), price)
2082 else:
2083 self.fail("I shouldn't have been called")
2084
2085 return {
2086 "id": symbol,
2087 }
2088 market.create_order.side_effect = create_order
2089 market.order_precision.return_value = 8
2090
2091 # Action 3
2092 portfolio.TradeStore.run_orders()
2093
2094 self.assertEqual("open", all_orders[0].status)
2095 self.assertEqual("open", all_orders[1].status)
2096
2097 market.fetch_order.return_value = { "status": "closed", "datetime": "2018-01-20 13:40:00" }
2098 market.privatePostReturnOrderTrades.return_value = [
2099 {
2100 "tradeID": 42, "type": "buy", "fee": "0.0015",
2101 "date": "2017-12-30 12:00:12", "rate": "0.1",
2102 "amount": "10", "total": "1"
2103 }
2104 ]
2105 with mock.patch.object(portfolio.time, "sleep") as sleep:
2106 # Action 4
2107 helper.follow_orders(verbose=False)
2108
2109 sleep.assert_called_with(30)
2110
2111 for order in all_orders:
2112 self.assertEqual("closed", order.status)
2113
2114 fetch_balance = {
2115 "ETH": {
2116 "exchange_free": D("1.0") / 3,
2117 "exchange_used": D("0.0"),
2118 "exchange_total": D("1.0") / 3,
2119 "margin_total": 0,
2120 "total": D("1.0") / 3,
2121 },
2122 "BTC": {
2123 "exchange_free": D("0.134"),
2124 "exchange_used": D("0.0"),
2125 "exchange_total": D("0.134"),
2126 "margin_total": 0,
2127 "total": D("0.134"),
2128 },
2129 "ETC": {
2130 "exchange_free": D("4.0"),
2131 "exchange_used": D("0.0"),
2132 "exchange_total": D("4.0"),
2133 "margin_total": 0,
2134 "total": D("4.0"),
2135 },
2136 "XVG": {
2137 "exchange_free": D("0.0"),
2138 "exchange_used": D("0.0"),
2139 "exchange_total": D("0.0"),
2140 "margin_total": 0,
2141 "total": D("0.0"),
2142 },
2143 }
2144 market.fetch_all_balances.return_value = fetch_balance
2145
2146 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
2147 # Action 5
2148 helper.update_trades(market, only="acquire", compute_value="average")
2149
2150 balances = portfolio.BalanceStore.all
2151 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
2152 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
2153 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
2154 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
2155
2156
2157 trades = portfolio.TradeStore.all
2158 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
2159 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
2160 self.assertEqual("dispose", trades[0].action)
2161
2162 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
2163 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
2164 self.assertEqual("acquire", trades[1].action)
2165
2166 self.assertNotIn("BTC", trades)
2167
2168 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
2169 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
2170 self.assertEqual("dispose", trades[2].action)
2171
2172 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
2173 self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
2174 self.assertEqual("acquire", trades[3].action)
2175
2176 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
2177 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
2178 self.assertEqual("acquire", trades[4].action)
2179
2180 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
2181 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
2182 self.assertEqual("acquire", trades[5].action)
2183
2184 # Action 6
2185 portfolio.TradeStore.prepare_orders(only="acquire", compute_value=lambda x, y: x["ask"])
2186
2187 all_orders = portfolio.TradeStore.all_orders(state="pending")
2188 self.assertEqual(4, len(all_orders))
2189 self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount))
2190 self.assertEqual(D("0.003"), all_orders[0].rate)
2191 self.assertEqual("buy", all_orders[0].action)
2192 self.assertEqual("long", all_orders[0].trade_type)
2193
2194 self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount))
2195 self.assertEqual(D("0.0012"), all_orders[1].rate)
2196 self.assertEqual("sell", all_orders[1].action)
2197 self.assertEqual("short", all_orders[1].trade_type)
2198
2199 diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount
2200 self.assertAlmostEqual(0, diff.value)
2201 self.assertEqual(D("0.0012"), all_orders[2].rate)
2202 self.assertEqual("buy", all_orders[2].action)
2203 self.assertEqual("long", all_orders[2].trade_type)
2204
2205 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount)
2206 self.assertEqual(D("16000"), all_orders[3].rate)
2207 self.assertEqual("sell", all_orders[3].action)
2208 self.assertEqual("long", all_orders[3].trade_type)
2209
2210 # Action 6b
2211 # TODO:
2212 # Move balances to margin
2213
2214 # Action 7
2215 # TODO
2216 # portfolio.TradeStore.run_orders()
2217
2218 with mock.patch.object(portfolio.time, "sleep") as sleep:
2219 # Action 8
2220 helper.follow_orders(verbose=False)
2221
2222 sleep.assert_called_with(30)
2223
2224 if __name__ == '__main__':
2225 unittest.main()