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