]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Fix move_balance not moving currencies absent from trades
[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 balance3 = portfolio.Balance("ETC", { "margin_free": "10" })
728 portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
729
730 market = mock.Mock()
731
732 helper.move_balances(market, debug=debug)
733
734 fetch_balances.assert_called_with(market)
735 if debug:
736 self.assertRegex(stdout_mock.getvalue(), "market.transfer_balance")
737 else:
738 market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
739 market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
740 market.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange")
741
742 @mock.patch.object(helper, "prepare_trades")
743 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
744 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
745 @mock.patch('sys.stdout', new_callable=StringIO)
746 def test_print_orders(self, stdout_mock, print_all_with_order, prepare_orders, prepare_trades):
747 market = mock.Mock()
748 portfolio.BalanceStore.all = {
749 "BTC": portfolio.Balance("BTC", {
750 "total": "0.65",
751 "exchange_total":"0.65",
752 "exchange_free": "0.35",
753 "exchange_used": "0.30"}),
754 "ETH": portfolio.Balance("ETH", {
755 "total": 3,
756 "exchange_total": 3,
757 "exchange_free": 3,
758 "exchange_used": 0}),
759 }
760 helper.print_orders(market)
761 prepare_trades.assert_called_with(market, base_currency="BTC",
762 compute_value="average", debug=True)
763 prepare_orders.assert_called_with(compute_value="average")
764 print_all_with_order.assert_called()
765 self.assertRegex(stdout_mock.getvalue(), "Balance")
766
767 @mock.patch.object(helper, "prepare_trades")
768 @mock.patch.object(helper, "follow_orders")
769 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
770 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
771 @mock.patch.object(portfolio.TradeStore, "run_orders")
772 @mock.patch('sys.stdout', new_callable=StringIO)
773 def test_process_sell_needed__1_sell(self, stdout_mock, run_orders,
774 print_all_with_order, prepare_orders, follow_orders,
775 prepare_trades):
776 market = mock.Mock()
777 portfolio.BalanceStore.all = {
778 "BTC": portfolio.Balance("BTC", {
779 "total": "0.65",
780 "exchange_total":"0.65",
781 "exchange_free": "0.35",
782 "exchange_used": "0.30"}),
783 "ETH": portfolio.Balance("ETH", {
784 "total": 3,
785 "exchange_total": 3,
786 "exchange_free": 3,
787 "exchange_used": 0}),
788 }
789 helper.process_sell_needed__1_sell(market)
790 prepare_trades.assert_called_with(market, base_currency="BTC",
791 debug=False)
792 prepare_orders.assert_called_with(compute_value="average",
793 only="dispose")
794 print_all_with_order.assert_called()
795 run_orders.assert_called()
796 follow_orders.assert_called()
797 self.assertRegex(stdout_mock.getvalue(), "Balance")
798
799 @mock.patch.object(helper, "update_trades")
800 @mock.patch.object(helper, "follow_orders")
801 @mock.patch.object(helper, "move_balances")
802 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
803 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
804 @mock.patch.object(portfolio.TradeStore, "run_orders")
805 @mock.patch('sys.stdout', new_callable=StringIO)
806 def test_process_sell_needed__2_buy(self, stdout_mock, run_orders,
807 print_all_with_order, prepare_orders, move_balances,
808 follow_orders, update_trades):
809 market = mock.Mock()
810 portfolio.BalanceStore.all = {
811 "BTC": portfolio.Balance("BTC", {
812 "total": "0.65",
813 "exchange_total":"0.65",
814 "exchange_free": "0.35",
815 "exchange_used": "0.30"}),
816 "ETH": portfolio.Balance("ETH", {
817 "total": 3,
818 "exchange_total": 3,
819 "exchange_free": 3,
820 "exchange_used": 0}),
821 }
822 helper.process_sell_needed__2_buy(market)
823 update_trades.assert_called_with(market, base_currency="BTC",
824 debug=False, only="acquire")
825 prepare_orders.assert_called_with(compute_value="average",
826 only="acquire")
827 print_all_with_order.assert_called()
828 move_balances.assert_called_with(market, debug=False)
829 run_orders.assert_called()
830 follow_orders.assert_called()
831 self.assertRegex(stdout_mock.getvalue(), "Balance")
832
833 @mock.patch.object(helper, "prepare_trades_to_sell_all")
834 @mock.patch.object(helper, "follow_orders")
835 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
836 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
837 @mock.patch.object(portfolio.TradeStore, "run_orders")
838 @mock.patch('sys.stdout', new_callable=StringIO)
839 def test_process_sell_all__1_sell(self, stdout_mock, run_orders,
840 print_all_with_order, prepare_orders, follow_orders,
841 prepare_trades_to_sell_all):
842 market = mock.Mock()
843 portfolio.BalanceStore.all = {
844 "BTC": portfolio.Balance("BTC", {
845 "total": "0.65",
846 "exchange_total":"0.65",
847 "exchange_free": "0.35",
848 "exchange_used": "0.30"}),
849 "ETH": portfolio.Balance("ETH", {
850 "total": 3,
851 "exchange_total": 3,
852 "exchange_free": 3,
853 "exchange_used": 0}),
854 }
855 helper.process_sell_all__1_all_sell(market)
856 prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
857 debug=False)
858 prepare_orders.assert_called_with(compute_value="average")
859 print_all_with_order.assert_called()
860 run_orders.assert_called()
861 follow_orders.assert_called()
862 self.assertRegex(stdout_mock.getvalue(), "Balance")
863
864 @mock.patch.object(helper, "prepare_trades")
865 @mock.patch.object(helper, "follow_orders")
866 @mock.patch.object(helper, "move_balances")
867 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
868 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
869 @mock.patch.object(portfolio.TradeStore, "run_orders")
870 @mock.patch('sys.stdout', new_callable=StringIO)
871 def test_process_sell_all__2_all_buy(self, stdout_mock, run_orders,
872 print_all_with_order, prepare_orders, move_balances,
873 follow_orders, prepare_trades):
874 market = mock.Mock()
875 portfolio.BalanceStore.all = {
876 "BTC": portfolio.Balance("BTC", {
877 "total": "0.65",
878 "exchange_total":"0.65",
879 "exchange_free": "0.35",
880 "exchange_used": "0.30"}),
881 "ETH": portfolio.Balance("ETH", {
882 "total": 3,
883 "exchange_total": 3,
884 "exchange_free": 3,
885 "exchange_used": 0}),
886 }
887 helper.process_sell_all__2_all_buy(market)
888 prepare_trades.assert_called_with(market, base_currency="BTC",
889 debug=False)
890 prepare_orders.assert_called_with()
891 print_all_with_order.assert_called()
892 move_balances.assert_called_with(market, debug=False)
893 run_orders.assert_called()
894 follow_orders.assert_called()
895 self.assertRegex(stdout_mock.getvalue(), "Balance")
896
897
898 @unittest.skipUnless("unit" in limits, "Unit skipped")
899 class TradeStoreTest(WebMockTestCase):
900 @mock.patch.object(portfolio.BalanceStore, "currencies")
901 @mock.patch.object(portfolio.TradeStore, "add_trade_if_matching")
902 def test_compute_trades(self, add_trade_if_matching, currencies):
903 currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
904
905 values_in_base = {
906 "XMR": portfolio.Amount("BTC", D("0.9")),
907 "DASH": portfolio.Amount("BTC", D("0.4")),
908 "XVG": portfolio.Amount("BTC", D("-0.5")),
909 "BTC": portfolio.Amount("BTC", D("0.5")),
910 }
911 new_repartition = {
912 "DASH": portfolio.Amount("BTC", D("0.5")),
913 "XVG": portfolio.Amount("BTC", D("0.1")),
914 "BTC": portfolio.Amount("BTC", D("0.4")),
915 "ETH": portfolio.Amount("BTC", D("0.3")),
916 }
917
918 portfolio.TradeStore.compute_trades(values_in_base,
919 new_repartition, only="only", market="market")
920
921 self.assertEqual(5, add_trade_if_matching.call_count)
922 add_trade_if_matching.assert_any_call(
923 portfolio.Amount("BTC", D("0.9")),
924 portfolio.Amount("BTC", 0),
925 "XMR", only="only", market="market"
926 )
927 add_trade_if_matching.assert_any_call(
928 portfolio.Amount("BTC", D("0.4")),
929 portfolio.Amount("BTC", D("0.5")),
930 "DASH", only="only", market="market"
931 )
932 add_trade_if_matching.assert_any_call(
933 portfolio.Amount("BTC", D("-0.5")),
934 portfolio.Amount("BTC", D("0")),
935 "XVG", only="only", market="market"
936 )
937 add_trade_if_matching.assert_any_call(
938 portfolio.Amount("BTC", D("0")),
939 portfolio.Amount("BTC", D("0.1")),
940 "XVG", only="only", market="market"
941 )
942 add_trade_if_matching.assert_any_call(
943 portfolio.Amount("BTC", D("0")),
944 portfolio.Amount("BTC", D("0.3")),
945 "ETH", only="only", market="market"
946 )
947
948 def test_add_trade_if_matching(self):
949 result = portfolio.TradeStore.add_trade_if_matching(
950 portfolio.Amount("BTC", D("0")),
951 portfolio.Amount("BTC", D("0.3")),
952 "ETH", only="nope", market="market"
953 )
954 self.assertEqual(0, len(portfolio.TradeStore.all))
955 self.assertEqual(False, result)
956
957 portfolio.TradeStore.all = []
958 result = portfolio.TradeStore.add_trade_if_matching(
959 portfolio.Amount("BTC", D("0")),
960 portfolio.Amount("BTC", D("0.3")),
961 "ETH", only=None, market="market"
962 )
963 self.assertEqual(1, len(portfolio.TradeStore.all))
964 self.assertEqual(True, result)
965
966 portfolio.TradeStore.all = []
967 result = portfolio.TradeStore.add_trade_if_matching(
968 portfolio.Amount("BTC", D("0")),
969 portfolio.Amount("BTC", D("0.3")),
970 "ETH", only="acquire", market="market"
971 )
972 self.assertEqual(1, len(portfolio.TradeStore.all))
973 self.assertEqual(True, result)
974
975 portfolio.TradeStore.all = []
976 result = portfolio.TradeStore.add_trade_if_matching(
977 portfolio.Amount("BTC", D("0")),
978 portfolio.Amount("BTC", D("0.3")),
979 "ETH", only="dispose", market="market"
980 )
981 self.assertEqual(0, len(portfolio.TradeStore.all))
982 self.assertEqual(False, result)
983
984 def test_prepare_orders(self):
985 trade_mock1 = mock.Mock()
986 trade_mock2 = mock.Mock()
987
988 portfolio.TradeStore.all.append(trade_mock1)
989 portfolio.TradeStore.all.append(trade_mock2)
990
991 portfolio.TradeStore.prepare_orders()
992 trade_mock1.prepare_order.assert_called_with(compute_value="default")
993 trade_mock2.prepare_order.assert_called_with(compute_value="default")
994
995 portfolio.TradeStore.prepare_orders(compute_value="bla")
996 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
997 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
998
999 trade_mock1.prepare_order.reset_mock()
1000 trade_mock2.prepare_order.reset_mock()
1001
1002 trade_mock1.action = "foo"
1003 trade_mock2.action = "bar"
1004 portfolio.TradeStore.prepare_orders(only="bar")
1005 trade_mock1.prepare_order.assert_not_called()
1006 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1007
1008 def test_print_all_with_order(self):
1009 trade_mock1 = mock.Mock()
1010 trade_mock2 = mock.Mock()
1011 trade_mock3 = mock.Mock()
1012 portfolio.TradeStore.all = [trade_mock1, trade_mock2, trade_mock3]
1013
1014 portfolio.TradeStore.print_all_with_order()
1015
1016 trade_mock1.print_with_order.assert_called()
1017 trade_mock2.print_with_order.assert_called()
1018 trade_mock3.print_with_order.assert_called()
1019
1020 @mock.patch.object(portfolio.TradeStore, "all_orders")
1021 def test_run_orders(self, all_orders):
1022 order_mock1 = mock.Mock()
1023 order_mock2 = mock.Mock()
1024 order_mock3 = mock.Mock()
1025 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1026 portfolio.TradeStore.run_orders()
1027 all_orders.assert_called_with(state="pending")
1028
1029 order_mock1.run.assert_called()
1030 order_mock2.run.assert_called()
1031 order_mock3.run.assert_called()
1032
1033 def test_all_orders(self):
1034 trade_mock1 = mock.Mock()
1035 trade_mock2 = mock.Mock()
1036
1037 order_mock1 = mock.Mock()
1038 order_mock2 = mock.Mock()
1039 order_mock3 = mock.Mock()
1040
1041 trade_mock1.orders = [order_mock1, order_mock2]
1042 trade_mock2.orders = [order_mock3]
1043
1044 order_mock1.status = "pending"
1045 order_mock2.status = "open"
1046 order_mock3.status = "open"
1047
1048 portfolio.TradeStore.all.append(trade_mock1)
1049 portfolio.TradeStore.all.append(trade_mock2)
1050
1051 orders = portfolio.TradeStore.all_orders()
1052 self.assertEqual(3, len(orders))
1053
1054 open_orders = portfolio.TradeStore.all_orders(state="open")
1055 self.assertEqual(2, len(open_orders))
1056 self.assertEqual([order_mock2, order_mock3], open_orders)
1057
1058 @mock.patch.object(portfolio.TradeStore, "all_orders")
1059 def test_update_all_orders_status(self, all_orders):
1060 order_mock1 = mock.Mock()
1061 order_mock2 = mock.Mock()
1062 order_mock3 = mock.Mock()
1063 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1064 portfolio.TradeStore.update_all_orders_status()
1065 all_orders.assert_called_with(state="open")
1066
1067 order_mock1.get_status.assert_called()
1068 order_mock2.get_status.assert_called()
1069 order_mock3.get_status.assert_called()
1070
1071 @unittest.skipUnless("unit" in limits, "Unit skipped")
1072 class BalanceStoreTest(WebMockTestCase):
1073 def setUp(self):
1074 super(BalanceStoreTest, self).setUp()
1075
1076 self.fetch_balance = {
1077 "ETC": {
1078 "exchange_free": 0,
1079 "exchange_used": 0,
1080 "exchange_total": 0,
1081 "margin_total": 0,
1082 },
1083 "USDT": {
1084 "exchange_free": D("6.0"),
1085 "exchange_used": D("1.2"),
1086 "exchange_total": D("7.2"),
1087 "margin_total": 0,
1088 },
1089 "XVG": {
1090 "exchange_free": 16,
1091 "exchange_used": 0,
1092 "exchange_total": 16,
1093 "margin_total": 0,
1094 },
1095 "XMR": {
1096 "exchange_free": 0,
1097 "exchange_used": 0,
1098 "exchange_total": 0,
1099 "margin_total": D("-1.0"),
1100 "margin_free": 0,
1101 },
1102 }
1103
1104 @mock.patch.object(helper, "get_ticker")
1105 def test_in_currency(self, get_ticker):
1106 portfolio.BalanceStore.all = {
1107 "BTC": portfolio.Balance("BTC", {
1108 "total": "0.65",
1109 "exchange_total":"0.65",
1110 "exchange_free": "0.35",
1111 "exchange_used": "0.30"}),
1112 "ETH": portfolio.Balance("ETH", {
1113 "total": 3,
1114 "exchange_total": 3,
1115 "exchange_free": 3,
1116 "exchange_used": 0}),
1117 }
1118 market = mock.Mock()
1119 get_ticker.return_value = {
1120 "bid": D("0.09"),
1121 "ask": D("0.11"),
1122 "average": D("0.1"),
1123 }
1124
1125 amounts = portfolio.BalanceStore.in_currency("BTC", market)
1126 self.assertEqual("BTC", amounts["ETH"].currency)
1127 self.assertEqual(D("0.65"), amounts["BTC"].value)
1128 self.assertEqual(D("0.30"), amounts["ETH"].value)
1129
1130 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid")
1131 self.assertEqual(D("0.65"), amounts["BTC"].value)
1132 self.assertEqual(D("0.27"), amounts["ETH"].value)
1133
1134 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used")
1135 self.assertEqual(D("0.30"), amounts["BTC"].value)
1136 self.assertEqual(0, amounts["ETH"].value)
1137
1138 def test_fetch_balances(self):
1139 market = mock.Mock()
1140 market.fetch_all_balances.return_value = self.fetch_balance
1141
1142 portfolio.BalanceStore.fetch_balances(market)
1143 self.assertNotIn("ETC", portfolio.BalanceStore.currencies())
1144 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.BalanceStore.currencies()))
1145
1146 portfolio.BalanceStore.all["ETC"] = portfolio.Balance("ETC", {
1147 "exchange_total": "1", "exchange_free": "0",
1148 "exchange_used": "1" })
1149 portfolio.BalanceStore.fetch_balances(market)
1150 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
1151 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))
1152
1153 @mock.patch.object(portfolio.Portfolio, "repartition")
1154 def test_dispatch_assets(self, repartition):
1155 market = mock.Mock()
1156 market.fetch_all_balances.return_value = self.fetch_balance
1157 portfolio.BalanceStore.fetch_balances(market)
1158
1159 self.assertNotIn("XEM", portfolio.BalanceStore.currencies())
1160
1161 repartition.return_value = {
1162 "XEM": (D("0.75"), "long"),
1163 "BTC": (D("0.26"), "long"),
1164 "DASH": (D("0.10"), "short"),
1165 }
1166
1167 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
1168 self.assertIn("XEM", portfolio.BalanceStore.currencies())
1169 self.assertEqual(D("2.6"), amounts["BTC"].value)
1170 self.assertEqual(D("7.5"), amounts["XEM"].value)
1171 self.assertEqual(D("-1.0"), amounts["DASH"].value)
1172
1173 def test_currencies(self):
1174 portfolio.BalanceStore.all = {
1175 "BTC": portfolio.Balance("BTC", {
1176 "total": "0.65",
1177 "exchange_total":"0.65",
1178 "exchange_free": "0.35",
1179 "exchange_used": "0.30"}),
1180 "ETH": portfolio.Balance("ETH", {
1181 "total": 3,
1182 "exchange_total": 3,
1183 "exchange_free": 3,
1184 "exchange_used": 0}),
1185 }
1186 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))
1187
1188 @unittest.skipUnless("unit" in limits, "Unit skipped")
1189 class ComputationTest(WebMockTestCase):
1190 def test_compute_value(self):
1191 compute = mock.Mock()
1192 portfolio.Computation.compute_value("foo", "buy", compute_value=compute)
1193 compute.assert_called_with("foo", "ask")
1194
1195 compute.reset_mock()
1196 portfolio.Computation.compute_value("foo", "sell", compute_value=compute)
1197 compute.assert_called_with("foo", "bid")
1198
1199 compute.reset_mock()
1200 portfolio.Computation.compute_value("foo", "ask", compute_value=compute)
1201 compute.assert_called_with("foo", "ask")
1202
1203 compute.reset_mock()
1204 portfolio.Computation.compute_value("foo", "bid", compute_value=compute)
1205 compute.assert_called_with("foo", "bid")
1206
1207 compute.reset_mock()
1208 portfolio.Computation.computations["test"] = compute
1209 portfolio.Computation.compute_value("foo", "bid", compute_value="test")
1210 compute.assert_called_with("foo", "bid")
1211
1212
1213 @unittest.skipUnless("unit" in limits, "Unit skipped")
1214 class TradeTest(WebMockTestCase):
1215
1216 def test_values_assertion(self):
1217 value_from = portfolio.Amount("BTC", "1.0")
1218 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1219 value_to = portfolio.Amount("BTC", "1.0")
1220 trade = portfolio.Trade(value_from, value_to, "ETH")
1221 self.assertEqual("BTC", trade.base_currency)
1222 self.assertEqual("ETH", trade.currency)
1223
1224 with self.assertRaises(AssertionError):
1225 portfolio.Trade(value_from, value_to, "ETC")
1226 with self.assertRaises(AssertionError):
1227 value_from.linked_to = None
1228 portfolio.Trade(value_from, value_to, "ETH")
1229 with self.assertRaises(AssertionError):
1230 value_from.currency = "ETH"
1231 portfolio.Trade(value_from, value_to, "ETH")
1232
1233 value_from = portfolio.Amount("BTC", 0)
1234 trade = portfolio.Trade(value_from, value_to, "ETH")
1235 self.assertEqual(0, trade.value_from.linked_to)
1236
1237 def test_action(self):
1238 value_from = portfolio.Amount("BTC", "1.0")
1239 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1240 value_to = portfolio.Amount("BTC", "1.0")
1241 trade = portfolio.Trade(value_from, value_to, "ETH")
1242
1243 self.assertIsNone(trade.action)
1244
1245 value_from = portfolio.Amount("BTC", "1.0")
1246 value_from.linked_to = portfolio.Amount("BTC", "1.0")
1247 value_to = portfolio.Amount("BTC", "2.0")
1248 trade = portfolio.Trade(value_from, value_to, "BTC")
1249
1250 self.assertIsNone(trade.action)
1251
1252 value_from = portfolio.Amount("BTC", "0.5")
1253 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1254 value_to = portfolio.Amount("BTC", "1.0")
1255 trade = portfolio.Trade(value_from, value_to, "ETH")
1256
1257 self.assertEqual("acquire", trade.action)
1258
1259 value_from = portfolio.Amount("BTC", "0")
1260 value_from.linked_to = portfolio.Amount("ETH", "0")
1261 value_to = portfolio.Amount("BTC", "-1.0")
1262 trade = portfolio.Trade(value_from, value_to, "ETH")
1263
1264 self.assertEqual("acquire", trade.action)
1265
1266 def test_order_action(self):
1267 value_from = portfolio.Amount("BTC", "0.5")
1268 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1269 value_to = portfolio.Amount("BTC", "1.0")
1270 trade = portfolio.Trade(value_from, value_to, "ETH")
1271
1272 self.assertEqual("buy", trade.order_action(False))
1273 self.assertEqual("sell", trade.order_action(True))
1274
1275 value_from = portfolio.Amount("BTC", "0")
1276 value_from.linked_to = portfolio.Amount("ETH", "0")
1277 value_to = portfolio.Amount("BTC", "-1.0")
1278 trade = portfolio.Trade(value_from, value_to, "ETH")
1279
1280 self.assertEqual("sell", trade.order_action(False))
1281 self.assertEqual("buy", trade.order_action(True))
1282
1283 def test_trade_type(self):
1284 value_from = portfolio.Amount("BTC", "0.5")
1285 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1286 value_to = portfolio.Amount("BTC", "1.0")
1287 trade = portfolio.Trade(value_from, value_to, "ETH")
1288
1289 self.assertEqual("long", trade.trade_type)
1290
1291 value_from = portfolio.Amount("BTC", "0")
1292 value_from.linked_to = portfolio.Amount("ETH", "0")
1293 value_to = portfolio.Amount("BTC", "-1.0")
1294 trade = portfolio.Trade(value_from, value_to, "ETH")
1295
1296 self.assertEqual("short", trade.trade_type)
1297
1298 def test_filled_amount(self):
1299 value_from = portfolio.Amount("BTC", "0.5")
1300 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1301 value_to = portfolio.Amount("BTC", "1.0")
1302 trade = portfolio.Trade(value_from, value_to, "ETH")
1303
1304 order1 = mock.Mock()
1305 order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")
1306
1307 order2 = mock.Mock()
1308 order2.filled_amount.return_value = portfolio.Amount("ETH", "0.01")
1309 trade.orders.append(order1)
1310 trade.orders.append(order2)
1311
1312 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount())
1313 order1.filled_amount.assert_called_with(in_base_currency=False)
1314 order2.filled_amount.assert_called_with(in_base_currency=False)
1315
1316 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
1317 order1.filled_amount.assert_called_with(in_base_currency=False)
1318 order2.filled_amount.assert_called_with(in_base_currency=False)
1319
1320 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
1321 order1.filled_amount.assert_called_with(in_base_currency=True)
1322 order2.filled_amount.assert_called_with(in_base_currency=True)
1323
1324 @mock.patch.object(helper, "get_ticker")
1325 @mock.patch.object(portfolio.Computation, "compute_value")
1326 @mock.patch.object(portfolio.Trade, "filled_amount")
1327 @mock.patch.object(portfolio, "Order")
1328 def test_prepare_order(self, Order, filled_amount, compute_value, get_ticker):
1329 Order.return_value = "Order"
1330
1331 with self.subTest(desc="Nothing to do"):
1332 value_from = portfolio.Amount("BTC", "10")
1333 value_from.rate = D("0.1")
1334 value_from.linked_to = portfolio.Amount("FOO", "100")
1335 value_to = portfolio.Amount("BTC", "10")
1336 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1337
1338 trade.prepare_order()
1339
1340 filled_amount.assert_not_called()
1341 compute_value.assert_not_called()
1342 self.assertEqual(0, len(trade.orders))
1343 Order.assert_not_called()
1344
1345 get_ticker.return_value = { "inverted": False }
1346 with self.subTest(desc="Already filled"), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1347 filled_amount.return_value = portfolio.Amount("FOO", "100")
1348 compute_value.return_value = D("0.125")
1349
1350 value_from = portfolio.Amount("BTC", "10")
1351 value_from.rate = D("0.1")
1352 value_from.linked_to = portfolio.Amount("FOO", "100")
1353 value_to = portfolio.Amount("BTC", "0")
1354 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1355
1356 trade.prepare_order()
1357
1358 filled_amount.assert_called_with(in_base_currency=False)
1359 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1360 self.assertEqual(0, len(trade.orders))
1361 self.assertRegex(stdout_mock.getvalue(), "Less to do than already filled: ")
1362 Order.assert_not_called()
1363
1364 with self.subTest(action="dispose", inverted=False):
1365 filled_amount.return_value = portfolio.Amount("FOO", "60")
1366 compute_value.return_value = D("0.125")
1367
1368 value_from = portfolio.Amount("BTC", "10")
1369 value_from.rate = D("0.1")
1370 value_from.linked_to = portfolio.Amount("FOO", "100")
1371 value_to = portfolio.Amount("BTC", "1")
1372 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1373
1374 trade.prepare_order()
1375
1376 filled_amount.assert_called_with(in_base_currency=False)
1377 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1378 self.assertEqual(1, len(trade.orders))
1379 Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
1380 D("0.125"), "BTC", "long", "market",
1381 trade, close_if_possible=False)
1382
1383 with self.subTest(action="acquire", inverted=False):
1384 filled_amount.return_value = portfolio.Amount("BTC", "3")
1385 compute_value.return_value = D("0.125")
1386
1387 value_from = portfolio.Amount("BTC", "1")
1388 value_from.rate = D("0.1")
1389 value_from.linked_to = portfolio.Amount("FOO", "10")
1390 value_to = portfolio.Amount("BTC", "10")
1391 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1392
1393 trade.prepare_order()
1394
1395 filled_amount.assert_called_with(in_base_currency=True)
1396 compute_value.assert_called_with(get_ticker.return_value, "buy", compute_value="default")
1397 self.assertEqual(1, len(trade.orders))
1398
1399 Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
1400 D("0.125"), "BTC", "long", "market",
1401 trade, close_if_possible=False)
1402
1403 with self.subTest(close_if_possible=True):
1404 filled_amount.return_value = portfolio.Amount("FOO", "0")
1405 compute_value.return_value = D("0.125")
1406
1407 value_from = portfolio.Amount("BTC", "10")
1408 value_from.rate = D("0.1")
1409 value_from.linked_to = portfolio.Amount("FOO", "100")
1410 value_to = portfolio.Amount("BTC", "0")
1411 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1412
1413 trade.prepare_order()
1414
1415 filled_amount.assert_called_with(in_base_currency=False)
1416 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1417 self.assertEqual(1, len(trade.orders))
1418 Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
1419 D("0.125"), "BTC", "long", "market",
1420 trade, close_if_possible=True)
1421
1422 get_ticker.return_value = { "inverted": True, "original": {} }
1423 with self.subTest(action="dispose", inverted=True):
1424 filled_amount.return_value = portfolio.Amount("FOO", "300")
1425 compute_value.return_value = D("125")
1426
1427 value_from = portfolio.Amount("BTC", "10")
1428 value_from.rate = D("0.01")
1429 value_from.linked_to = portfolio.Amount("FOO", "1000")
1430 value_to = portfolio.Amount("BTC", "1")
1431 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1432
1433 trade.prepare_order(compute_value="foo")
1434
1435 filled_amount.assert_called_with(in_base_currency=True)
1436 compute_value.assert_called_with(get_ticker.return_value["original"], "buy", compute_value="foo")
1437 self.assertEqual(1, len(trade.orders))
1438 Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
1439 D("125"), "FOO", "long", "market",
1440 trade, close_if_possible=False)
1441
1442 with self.subTest(action="acquire", inverted=True):
1443 filled_amount.return_value = portfolio.Amount("BTC", "4")
1444 compute_value.return_value = D("125")
1445
1446 value_from = portfolio.Amount("BTC", "1")
1447 value_from.rate = D("0.01")
1448 value_from.linked_to = portfolio.Amount("FOO", "100")
1449 value_to = portfolio.Amount("BTC", "10")
1450 trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
1451
1452 trade.prepare_order(compute_value="foo")
1453
1454 filled_amount.assert_called_with(in_base_currency=False)
1455 compute_value.assert_called_with(get_ticker.return_value["original"], "sell", compute_value="foo")
1456 self.assertEqual(1, len(trade.orders))
1457 Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
1458 D("125"), "FOO", "long", "market",
1459 trade, close_if_possible=False)
1460
1461
1462 @mock.patch.object(portfolio.Trade, "prepare_order")
1463 def test_update_order(self, prepare_order):
1464 order_mock = mock.Mock()
1465 new_order_mock = mock.Mock()
1466
1467 value_from = portfolio.Amount("BTC", "0.5")
1468 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1469 value_to = portfolio.Amount("BTC", "1.0")
1470 trade = portfolio.Trade(value_from, value_to, "ETH")
1471 prepare_order.return_value = new_order_mock
1472
1473 for i in [0, 1, 3, 4, 6]:
1474 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1475 trade.update_order(order_mock, i)
1476 order_mock.cancel.assert_not_called()
1477 new_order_mock.run.assert_not_called()
1478 self.assertRegex(stdout_mock.getvalue(), "tick {}, waiting".format(i))
1479
1480 order_mock.reset_mock()
1481 new_order_mock.reset_mock()
1482 trade.orders = []
1483
1484 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1485 trade.update_order(order_mock, 2)
1486 order_mock.cancel.assert_called()
1487 new_order_mock.run.assert_called()
1488 prepare_order.assert_called()
1489 self.assertRegex(stdout_mock.getvalue(), "tick 2, cancelling and adjusting")
1490
1491 order_mock.reset_mock()
1492 new_order_mock.reset_mock()
1493 trade.orders = []
1494
1495 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1496 trade.update_order(order_mock, 5)
1497 order_mock.cancel.assert_called()
1498 new_order_mock.run.assert_called()
1499 prepare_order.assert_called()
1500 self.assertRegex(stdout_mock.getvalue(), "tick 5, cancelling and adjusting")
1501
1502 order_mock.reset_mock()
1503 new_order_mock.reset_mock()
1504 trade.orders = []
1505
1506 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1507 trade.update_order(order_mock, 7)
1508 order_mock.cancel.assert_called()
1509 new_order_mock.run.assert_called()
1510 prepare_order.assert_called_with(compute_value="default")
1511 self.assertRegex(stdout_mock.getvalue(), "tick 7, fallbacking to market value")
1512 self.assertRegex(stdout_mock.getvalue(), "tick 7, market value, cancelling and adjusting to")
1513
1514 order_mock.reset_mock()
1515 new_order_mock.reset_mock()
1516 trade.orders = []
1517
1518 for i in [10, 13, 16]:
1519 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1520 trade.update_order(order_mock, i)
1521 order_mock.cancel.assert_called()
1522 new_order_mock.run.assert_called()
1523 prepare_order.assert_called_with(compute_value="default")
1524 self.assertNotRegex(stdout_mock.getvalue(), "tick {}, fallbacking to market value".format(i))
1525 self.assertRegex(stdout_mock.getvalue(), "tick {}, market value, cancelling and adjusting to".format(i))
1526
1527 order_mock.reset_mock()
1528 new_order_mock.reset_mock()
1529 trade.orders = []
1530
1531 for i in [8, 9, 11, 12]:
1532 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1533 trade.update_order(order_mock, i)
1534 order_mock.cancel.assert_not_called()
1535 new_order_mock.run.assert_not_called()
1536 self.assertEqual("", stdout_mock.getvalue())
1537
1538 order_mock.reset_mock()
1539 new_order_mock.reset_mock()
1540 trade.orders = []
1541
1542
1543 @mock.patch('sys.stdout', new_callable=StringIO)
1544 def test_print_with_order(self, mock_stdout):
1545 value_from = portfolio.Amount("BTC", "0.5")
1546 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1547 value_to = portfolio.Amount("BTC", "1.0")
1548 trade = portfolio.Trade(value_from, value_to, "ETH")
1549
1550 order_mock1 = mock.Mock()
1551 order_mock1.__repr__ = mock.Mock()
1552 order_mock1.__repr__.return_value = "Mock 1"
1553 order_mock2 = mock.Mock()
1554 order_mock2.__repr__ = mock.Mock()
1555 order_mock2.__repr__.return_value = "Mock 2"
1556 trade.orders.append(order_mock1)
1557 trade.orders.append(order_mock2)
1558
1559 trade.print_with_order()
1560
1561 out = mock_stdout.getvalue().split("\n")
1562 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0])
1563 self.assertEqual("\tMock 1", out[1])
1564 self.assertEqual("\tMock 2", out[2])
1565
1566 def test__repr(self):
1567 value_from = portfolio.Amount("BTC", "0.5")
1568 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1569 value_to = portfolio.Amount("BTC", "1.0")
1570 trade = portfolio.Trade(value_from, value_to, "ETH")
1571
1572 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
1573
1574 @unittest.skipUnless("unit" in limits, "Unit skipped")
1575 class OrderTest(WebMockTestCase):
1576 def test_values(self):
1577 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1578 D("0.1"), "BTC", "long", "market", "trade")
1579 self.assertEqual("buy", order.action)
1580 self.assertEqual(10, order.amount.value)
1581 self.assertEqual("ETH", order.amount.currency)
1582 self.assertEqual(D("0.1"), order.rate)
1583 self.assertEqual("BTC", order.base_currency)
1584 self.assertEqual("market", order.market)
1585 self.assertEqual("long", order.trade_type)
1586 self.assertEqual("pending", order.status)
1587 self.assertEqual("trade", order.trade)
1588 self.assertIsNone(order.id)
1589 self.assertFalse(order.close_if_possible)
1590
1591 def test__repr(self):
1592 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1593 D("0.1"), "BTC", "long", "market", "trade")
1594 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order))
1595
1596 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1597 D("0.1"), "BTC", "long", "market", "trade",
1598 close_if_possible=True)
1599 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order))
1600
1601 def test_account(self):
1602 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1603 D("0.1"), "BTC", "long", "market", "trade")
1604 self.assertEqual("exchange", order.account)
1605
1606 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
1607 D("0.1"), "BTC", "short", "market", "trade")
1608 self.assertEqual("margin", order.account)
1609
1610 def test_pending(self):
1611 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1612 D("0.1"), "BTC", "long", "market", "trade")
1613 self.assertTrue(order.pending)
1614 order.status = "open"
1615 self.assertFalse(order.pending)
1616
1617 def test_open(self):
1618 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1619 D("0.1"), "BTC", "long", "market", "trade")
1620 self.assertFalse(order.open)
1621 order.status = "open"
1622 self.assertTrue(order.open)
1623
1624 def test_finished(self):
1625 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1626 D("0.1"), "BTC", "long", "market", "trade")
1627 self.assertFalse(order.finished)
1628 order.status = "closed"
1629 self.assertTrue(order.finished)
1630 order.status = "canceled"
1631 self.assertTrue(order.finished)
1632 order.status = "error"
1633 self.assertTrue(order.finished)
1634
1635 @mock.patch.object(portfolio.Order, "fetch")
1636 def test_cancel(self, fetch):
1637 market = mock.Mock()
1638 portfolio.TradeStore.debug = True
1639 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1640 D("0.1"), "BTC", "long", market, "trade")
1641 order.status = "open"
1642
1643 order.cancel()
1644 market.cancel_order.assert_not_called()
1645 self.assertEqual("canceled", order.status)
1646
1647 portfolio.TradeStore.debug = False
1648 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1649 D("0.1"), "BTC", "long", market, "trade")
1650 order.status = "open"
1651 order.id = 42
1652
1653 order.cancel()
1654 market.cancel_order.assert_called_with(42)
1655 fetch.assert_called_once()
1656
1657 def test_dust_amount_remaining(self):
1658 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1659 D("0.1"), "BTC", "long", "market", "trade")
1660 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
1661 self.assertFalse(order.dust_amount_remaining())
1662
1663 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001")))
1664 self.assertTrue(order.dust_amount_remaining())
1665
1666 @mock.patch.object(portfolio.Order, "fetch")
1667 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
1668 def test_remaining_amount(self, filled_amount, fetch):
1669 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1670 D("0.1"), "BTC", "long", "market", "trade")
1671
1672 self.assertEqual(9, order.remaining_amount().value)
1673 order.fetch.assert_not_called()
1674
1675 order.status = "open"
1676 self.assertEqual(9, order.remaining_amount().value)
1677 fetch.assert_called_once()
1678
1679 @mock.patch.object(portfolio.Order, "fetch")
1680 def test_filled_amount(self, fetch):
1681 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1682 D("0.1"), "BTC", "long", "market", "trade")
1683 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
1684 "tradeID": 42, "type": "buy", "fee": "0.0015",
1685 "date": "2017-12-30 12:00:12", "rate": "0.1",
1686 "amount": "3", "total": "0.3"
1687 }))
1688 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
1689 "tradeID": 43, "type": "buy", "fee": "0.0015",
1690 "date": "2017-12-30 13:00:12", "rate": "0.2",
1691 "amount": "2", "total": "0.4"
1692 }))
1693 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount())
1694 fetch.assert_not_called()
1695 order.status = "open"
1696 self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False))
1697 fetch.assert_called_once()
1698 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))
1699
1700 def test_fetch_mouvements(self):
1701 market = mock.Mock()
1702 market.privatePostReturnOrderTrades.return_value = [
1703 {
1704 "tradeID": 42, "type": "buy", "fee": "0.0015",
1705 "date": "2017-12-30 12:00:12", "rate": "0.1",
1706 "amount": "3", "total": "0.3"
1707 },
1708 {
1709 "tradeID": 43, "type": "buy", "fee": "0.0015",
1710 "date": "2017-12-30 13:00:12", "rate": "0.2",
1711 "amount": "2", "total": "0.4"
1712 }
1713 ]
1714 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1715 D("0.1"), "BTC", "long", market, "trade")
1716 order.id = 12
1717 order.mouvements = ["Foo", "Bar", "Baz"]
1718
1719 order.fetch_mouvements()
1720
1721 market.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
1722 self.assertEqual(2, len(order.mouvements))
1723 self.assertEqual(42, order.mouvements[0].id)
1724 self.assertEqual(43, order.mouvements[1].id)
1725
1726 market.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
1727 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1728 D("0.1"), "BTC", "long", market, "trade")
1729 order.fetch_mouvements()
1730 self.assertEqual(0, len(order.mouvements))
1731
1732 def test_mark_finished_order(self):
1733 market = mock.Mock()
1734 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1735 D("0.1"), "BTC", "short", market, "trade",
1736 close_if_possible=True)
1737 order.status = "closed"
1738 portfolio.TradeStore.debug = False
1739
1740 order.mark_finished_order()
1741 market.close_margin_position.assert_called_with("ETH", "BTC")
1742 market.close_margin_position.reset_mock()
1743
1744 order.status = "open"
1745 order.mark_finished_order()
1746 market.close_margin_position.assert_not_called()
1747
1748 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1749 D("0.1"), "BTC", "short", market, "trade",
1750 close_if_possible=False)
1751 order.status = "closed"
1752 order.mark_finished_order()
1753 market.close_margin_position.assert_not_called()
1754
1755 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
1756 D("0.1"), "BTC", "short", market, "trade",
1757 close_if_possible=True)
1758 order.status = "closed"
1759 order.mark_finished_order()
1760 market.close_margin_position.assert_not_called()
1761
1762 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1763 D("0.1"), "BTC", "long", market, "trade",
1764 close_if_possible=True)
1765 order.status = "closed"
1766 order.mark_finished_order()
1767 market.close_margin_position.assert_not_called()
1768
1769 portfolio.TradeStore.debug = True
1770
1771 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1772 D("0.1"), "BTC", "short", market, "trade",
1773 close_if_possible=True)
1774 order.status = "closed"
1775
1776 order.mark_finished_order()
1777 market.close_margin_position.assert_not_called()
1778
1779 @mock.patch.object(portfolio.Order, "fetch_mouvements")
1780 def test_fetch(self, fetch_mouvements):
1781 time = self.time.time()
1782 with mock.patch.object(portfolio.time, "time") as time_mock:
1783 market = mock.Mock()
1784 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1785 D("0.1"), "BTC", "long", market, "trade")
1786 order.id = 45
1787 with self.subTest(debug=True):
1788 portfolio.TradeStore.debug = True
1789 order.fetch()
1790 time_mock.assert_not_called()
1791 order.fetch(force=True)
1792 time_mock.assert_not_called()
1793 market.fetch_order.assert_not_called()
1794 fetch_mouvements.assert_not_called()
1795 self.assertIsNone(order.fetch_cache_timestamp)
1796
1797 with self.subTest(debug=False):
1798 portfolio.TradeStore.debug = False
1799 time_mock.return_value = time
1800 market.fetch_order.return_value = {
1801 "status": "foo",
1802 "datetime": "timestamp"
1803 }
1804 order.fetch()
1805
1806 market.fetch_order.assert_called_once()
1807 fetch_mouvements.assert_called_once()
1808 self.assertEqual("foo", order.status)
1809 self.assertEqual("timestamp", order.timestamp)
1810 self.assertEqual(time, order.fetch_cache_timestamp)
1811 self.assertEqual(1, len(order.results))
1812
1813 market.fetch_order.reset_mock()
1814 fetch_mouvements.reset_mock()
1815
1816 time_mock.return_value = time + 8
1817 order.fetch()
1818 market.fetch_order.assert_not_called()
1819 fetch_mouvements.assert_not_called()
1820
1821 order.fetch(force=True)
1822 market.fetch_order.assert_called_once()
1823 fetch_mouvements.assert_called_once()
1824
1825 market.fetch_order.reset_mock()
1826 fetch_mouvements.reset_mock()
1827
1828 time_mock.return_value = time + 19
1829 order.fetch()
1830 market.fetch_order.assert_called_once()
1831 fetch_mouvements.assert_called_once()
1832
1833 @mock.patch.object(portfolio.Order, "fetch")
1834 @mock.patch.object(portfolio.Order, "mark_finished_order")
1835 def test_get_status(self, mark_finished_order, fetch):
1836 with self.subTest(debug=True):
1837 portfolio.TradeStore.debug = True
1838 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1839 D("0.1"), "BTC", "long", "market", "trade")
1840 self.assertEqual("pending", order.get_status())
1841 fetch.assert_not_called()
1842
1843 with self.subTest(debug=False, finished=False):
1844 portfolio.TradeStore.debug = False
1845 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1846 D("0.1"), "BTC", "long", "market", "trade")
1847 def _fetch(order):
1848 def update_status():
1849 order.status = "open"
1850 return update_status
1851 fetch.side_effect = _fetch(order)
1852 self.assertEqual("open", order.get_status())
1853 mark_finished_order.assert_not_called()
1854 fetch.assert_called_once()
1855
1856 mark_finished_order.reset_mock()
1857 fetch.reset_mock()
1858 with self.subTest(debug=False, finished=True):
1859 portfolio.TradeStore.debug = False
1860 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1861 D("0.1"), "BTC", "long", "market", "trade")
1862 def _fetch(order):
1863 def update_status():
1864 order.status = "closed"
1865 return update_status
1866 fetch.side_effect = _fetch(order)
1867 self.assertEqual("closed", order.get_status())
1868 mark_finished_order.assert_called_once()
1869 fetch.assert_called_once()
1870
1871 def test_run(self):
1872 market = mock.Mock()
1873
1874 market.order_precision.return_value = 4
1875 with self.subTest(debug=True),\
1876 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1877 portfolio.TradeStore.debug = True
1878 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1879 D("0.1"), "BTC", "long", market, "trade")
1880 order.run()
1881 market.create_order.assert_not_called()
1882 self.assertEqual("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)\n", stdout_mock.getvalue())
1883 self.assertEqual("open", order.status)
1884 self.assertEqual(1, len(order.results))
1885 self.assertEqual(-1, order.id)
1886
1887 market.create_order.reset_mock()
1888 with self.subTest(debug=False):
1889 portfolio.TradeStore.debug = False
1890 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1891 D("0.1"), "BTC", "long", market, "trade")
1892 market.create_order.return_value = { "id": 123 }
1893 order.run()
1894 market.create_order.assert_called_once()
1895 self.assertEqual(1, len(order.results))
1896 self.assertEqual("open", order.status)
1897
1898 market.create_order.reset_mock()
1899 with self.subTest(exception=True),\
1900 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
1901 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1902 D("0.1"), "BTC", "long", market, "trade")
1903 market.create_order.side_effect = Exception("bouh")
1904 order.run()
1905 market.create_order.assert_called_once()
1906 self.assertEqual(0, len(order.results))
1907 self.assertEqual("error", order.status)
1908 self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order")
1909 self.assertRegex(stdout_mock.getvalue(), "Exception: bouh")
1910
1911 market.create_order.reset_mock()
1912 with self.subTest(dust_amount_exception=True),\
1913 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
1914 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
1915 D("0.1"), "BTC", "long", market, "trade")
1916 market.create_order.side_effect = portfolio.ExchangeNotAvailable
1917 order.run()
1918 market.create_order.assert_called_once()
1919 self.assertEqual(0, len(order.results))
1920 self.assertEqual("closed", order.status)
1921 mark_finished_order.assert_called_once()
1922
1923
1924 @unittest.skipUnless("unit" in limits, "Unit skipped")
1925 class MouvementTest(WebMockTestCase):
1926 def test_values(self):
1927 mouvement = portfolio.Mouvement("ETH", "BTC", {
1928 "tradeID": 42, "type": "buy", "fee": "0.0015",
1929 "date": "2017-12-30 12:00:12", "rate": "0.1",
1930 "amount": "10", "total": "1"
1931 })
1932 self.assertEqual("ETH", mouvement.currency)
1933 self.assertEqual("BTC", mouvement.base_currency)
1934 self.assertEqual(42, mouvement.id)
1935 self.assertEqual("buy", mouvement.action)
1936 self.assertEqual(D("0.0015"), mouvement.fee_rate)
1937 self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), mouvement.date)
1938 self.assertEqual(D("0.1"), mouvement.rate)
1939 self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
1940 self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)
1941
1942 mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
1943 self.assertIsNone(mouvement.date)
1944 self.assertIsNone(mouvement.id)
1945 self.assertIsNone(mouvement.action)
1946 self.assertEqual(-1, mouvement.fee_rate)
1947 self.assertEqual(0, mouvement.rate)
1948 self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
1949 self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)
1950
1951 @unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
1952 class AcceptanceTest(WebMockTestCase):
1953 @unittest.expectedFailure
1954 def test_success_sell_only_necessary(self):
1955 fetch_balance = {
1956 "ETH": {
1957 "exchange_free": D("1.0"),
1958 "exchange_used": D("0.0"),
1959 "exchange_total": D("1.0"),
1960 "total": D("1.0"),
1961 },
1962 "ETC": {
1963 "exchange_free": D("4.0"),
1964 "exchange_used": D("0.0"),
1965 "exchange_total": D("4.0"),
1966 "total": D("4.0"),
1967 },
1968 "XVG": {
1969 "exchange_free": D("1000.0"),
1970 "exchange_used": D("0.0"),
1971 "exchange_total": D("1000.0"),
1972 "total": D("1000.0"),
1973 },
1974 }
1975 repartition = {
1976 "ETH": (D("0.25"), "long"),
1977 "ETC": (D("0.25"), "long"),
1978 "BTC": (D("0.4"), "long"),
1979 "BTD": (D("0.01"), "short"),
1980 "B2X": (D("0.04"), "long"),
1981 "USDT": (D("0.05"), "long"),
1982 }
1983
1984 def fetch_ticker(symbol):
1985 if symbol == "ETH/BTC":
1986 return {
1987 "symbol": "ETH/BTC",
1988 "bid": D("0.14"),
1989 "ask": D("0.16")
1990 }
1991 if symbol == "ETC/BTC":
1992 return {
1993 "symbol": "ETC/BTC",
1994 "bid": D("0.002"),
1995 "ask": D("0.003")
1996 }
1997 if symbol == "XVG/BTC":
1998 return {
1999 "symbol": "XVG/BTC",
2000 "bid": D("0.00003"),
2001 "ask": D("0.00005")
2002 }
2003 if symbol == "BTD/BTC":
2004 return {
2005 "symbol": "BTD/BTC",
2006 "bid": D("0.0008"),
2007 "ask": D("0.0012")
2008 }
2009 if symbol == "B2X/BTC":
2010 return {
2011 "symbol": "B2X/BTC",
2012 "bid": D("0.0008"),
2013 "ask": D("0.0012")
2014 }
2015 if symbol == "USDT/BTC":
2016 raise helper.ExchangeError
2017 if symbol == "BTC/USDT":
2018 return {
2019 "symbol": "BTC/USDT",
2020 "bid": D("14000"),
2021 "ask": D("16000")
2022 }
2023 self.fail("Shouldn't have been called with {}".format(symbol))
2024
2025 market = mock.Mock()
2026 market.fetch_all_balances.return_value = fetch_balance
2027 market.fetch_ticker.side_effect = fetch_ticker
2028 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
2029 # Action 1
2030 helper.prepare_trades(market)
2031
2032 balances = portfolio.BalanceStore.all
2033 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
2034 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
2035 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
2036
2037
2038 trades = portfolio.TradeStore.all
2039 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
2040 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
2041 self.assertEqual("dispose", trades[0].action)
2042
2043 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
2044 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
2045 self.assertEqual("acquire", trades[1].action)
2046
2047 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
2048 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
2049 self.assertEqual("dispose", trades[2].action)
2050
2051 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
2052 self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
2053 self.assertEqual("acquire", trades[3].action)
2054
2055 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
2056 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
2057 self.assertEqual("acquire", trades[4].action)
2058
2059 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
2060 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
2061 self.assertEqual("acquire", trades[5].action)
2062
2063 # Action 2
2064 portfolio.TradeStore.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001"))
2065
2066 all_orders = portfolio.TradeStore.all_orders(state="pending")
2067 self.assertEqual(2, len(all_orders))
2068 self.assertEqual(2, 3*all_orders[0].amount.value)
2069 self.assertEqual(D("0.14014"), all_orders[0].rate)
2070 self.assertEqual(1000, all_orders[1].amount.value)
2071 self.assertEqual(D("0.00003003"), all_orders[1].rate)
2072
2073
2074 def create_order(symbol, type, action, amount, price=None, account="exchange"):
2075 self.assertEqual("limit", type)
2076 if symbol == "ETH/BTC":
2077 self.assertEqual("sell", action)
2078 self.assertEqual(D('0.66666666'), amount)
2079 self.assertEqual(D("0.14014"), price)
2080 elif symbol == "XVG/BTC":
2081 self.assertEqual("sell", action)
2082 self.assertEqual(1000, amount)
2083 self.assertEqual(D("0.00003003"), price)
2084 else:
2085 self.fail("I shouldn't have been called")
2086
2087 return {
2088 "id": symbol,
2089 }
2090 market.create_order.side_effect = create_order
2091 market.order_precision.return_value = 8
2092
2093 # Action 3
2094 portfolio.TradeStore.run_orders()
2095
2096 self.assertEqual("open", all_orders[0].status)
2097 self.assertEqual("open", all_orders[1].status)
2098
2099 market.fetch_order.return_value = { "status": "closed", "datetime": "2018-01-20 13:40:00" }
2100 market.privatePostReturnOrderTrades.return_value = [
2101 {
2102 "tradeID": 42, "type": "buy", "fee": "0.0015",
2103 "date": "2017-12-30 12:00:12", "rate": "0.1",
2104 "amount": "10", "total": "1"
2105 }
2106 ]
2107 with mock.patch.object(portfolio.time, "sleep") as sleep:
2108 # Action 4
2109 helper.follow_orders(verbose=False)
2110
2111 sleep.assert_called_with(30)
2112
2113 for order in all_orders:
2114 self.assertEqual("closed", order.status)
2115
2116 fetch_balance = {
2117 "ETH": {
2118 "exchange_free": D("1.0") / 3,
2119 "exchange_used": D("0.0"),
2120 "exchange_total": D("1.0") / 3,
2121 "margin_total": 0,
2122 "total": D("1.0") / 3,
2123 },
2124 "BTC": {
2125 "exchange_free": D("0.134"),
2126 "exchange_used": D("0.0"),
2127 "exchange_total": D("0.134"),
2128 "margin_total": 0,
2129 "total": D("0.134"),
2130 },
2131 "ETC": {
2132 "exchange_free": D("4.0"),
2133 "exchange_used": D("0.0"),
2134 "exchange_total": D("4.0"),
2135 "margin_total": 0,
2136 "total": D("4.0"),
2137 },
2138 "XVG": {
2139 "exchange_free": D("0.0"),
2140 "exchange_used": D("0.0"),
2141 "exchange_total": D("0.0"),
2142 "margin_total": 0,
2143 "total": D("0.0"),
2144 },
2145 }
2146 market.fetch_all_balances.return_value = fetch_balance
2147
2148 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
2149 # Action 5
2150 helper.update_trades(market, only="acquire", compute_value="average")
2151
2152 balances = portfolio.BalanceStore.all
2153 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
2154 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
2155 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
2156 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
2157
2158
2159 trades = portfolio.TradeStore.all
2160 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
2161 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
2162 self.assertEqual("dispose", trades[0].action)
2163
2164 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
2165 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
2166 self.assertEqual("acquire", trades[1].action)
2167
2168 self.assertNotIn("BTC", trades)
2169
2170 self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
2171 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
2172 self.assertEqual("dispose", trades[2].action)
2173
2174 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
2175 self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
2176 self.assertEqual("acquire", trades[3].action)
2177
2178 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
2179 self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
2180 self.assertEqual("acquire", trades[4].action)
2181
2182 self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
2183 self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
2184 self.assertEqual("acquire", trades[5].action)
2185
2186 # Action 6
2187 portfolio.TradeStore.prepare_orders(only="acquire", compute_value=lambda x, y: x["ask"])
2188
2189 all_orders = portfolio.TradeStore.all_orders(state="pending")
2190 self.assertEqual(4, len(all_orders))
2191 self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount))
2192 self.assertEqual(D("0.003"), all_orders[0].rate)
2193 self.assertEqual("buy", all_orders[0].action)
2194 self.assertEqual("long", all_orders[0].trade_type)
2195
2196 self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount))
2197 self.assertEqual(D("0.0012"), all_orders[1].rate)
2198 self.assertEqual("sell", all_orders[1].action)
2199 self.assertEqual("short", all_orders[1].trade_type)
2200
2201 diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount
2202 self.assertAlmostEqual(0, diff.value)
2203 self.assertEqual(D("0.0012"), all_orders[2].rate)
2204 self.assertEqual("buy", all_orders[2].action)
2205 self.assertEqual("long", all_orders[2].trade_type)
2206
2207 self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount)
2208 self.assertEqual(D("16000"), all_orders[3].rate)
2209 self.assertEqual("sell", all_orders[3].action)
2210 self.assertEqual("long", all_orders[3].trade_type)
2211
2212 # Action 6b
2213 # TODO:
2214 # Move balances to margin
2215
2216 # Action 7
2217 # TODO
2218 # portfolio.TradeStore.run_orders()
2219
2220 with mock.patch.object(portfolio.time, "sleep") as sleep:
2221 # Action 8
2222 helper.follow_orders(verbose=False)
2223
2224 sleep.assert_called_with(30)
2225
2226 if __name__ == '__main__':
2227 unittest.main()