diff options
-rw-r--r-- | market.py | 8 | ||||
-rw-r--r-- | portfolio.py | 79 | ||||
-rw-r--r-- | store.py | 85 | ||||
-rw-r--r-- | test.py | 203 |
4 files changed, 177 insertions, 198 deletions
@@ -312,7 +312,7 @@ class Processor: | |||
312 | import inspect | 312 | import inspect |
313 | 313 | ||
314 | if action == "wait_for_recent": | 314 | if action == "wait_for_recent": |
315 | method = portfolio.Portfolio.wait_for_recent | 315 | method = Portfolio.wait_for_recent |
316 | elif action == "prepare_trades": | 316 | elif action == "prepare_trades": |
317 | method = self.market.prepare_trades | 317 | method = self.market.prepare_trades |
318 | elif action == "prepare_orders": | 318 | elif action == "prepare_orders": |
@@ -345,8 +345,4 @@ class Processor: | |||
345 | def run_action(self, action, default_args, kwargs): | 345 | def run_action(self, action, default_args, kwargs): |
346 | method, args = self.parse_args(action, default_args, kwargs) | 346 | method, args = self.parse_args(action, default_args, kwargs) |
347 | 347 | ||
348 | if action == "wait_for_recent": | 348 | method(**args) |
349 | method(self.market, **args) | ||
350 | else: | ||
351 | method(**args) | ||
352 | |||
diff --git a/portfolio.py b/portfolio.py index 0f2c011..554b34f 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -1,87 +1,10 @@ | |||
1 | import time | 1 | from datetime import datetime |
2 | from datetime import datetime, timedelta | ||
3 | from decimal import Decimal as D, ROUND_DOWN | 2 | from decimal import Decimal as D, ROUND_DOWN |
4 | from json import JSONDecodeError | ||
5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | ||
6 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound | 3 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound |
7 | from retry import retry | 4 | from retry import retry |
8 | import requests | ||
9 | 5 | ||
10 | # FIXME: correctly handle web call timeouts | 6 | # FIXME: correctly handle web call timeouts |
11 | 7 | ||
12 | class Portfolio: | ||
13 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" | ||
14 | liquidities = {} | ||
15 | data = None | ||
16 | last_date = None | ||
17 | |||
18 | @classmethod | ||
19 | def wait_for_recent(cls, market, delta=4): | ||
20 | cls.repartition(market, refetch=True) | ||
21 | while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): | ||
22 | time.sleep(30) | ||
23 | market.report.print_log("Attempt to fetch up-to-date cryptoportfolio") | ||
24 | cls.repartition(market, refetch=True) | ||
25 | |||
26 | @classmethod | ||
27 | def repartition(cls, market, liquidity="medium", refetch=False): | ||
28 | cls.parse_cryptoportfolio(market, refetch=refetch) | ||
29 | liquidities = cls.liquidities[liquidity] | ||
30 | return liquidities[cls.last_date] | ||
31 | |||
32 | @classmethod | ||
33 | def get_cryptoportfolio(cls, market): | ||
34 | try: | ||
35 | r = requests.get(cls.URL) | ||
36 | market.report.log_http_request(r.request.method, | ||
37 | r.request.url, r.request.body, r.request.headers, r) | ||
38 | except Exception as e: | ||
39 | market.report.log_error("get_cryptoportfolio", exception=e) | ||
40 | return | ||
41 | try: | ||
42 | cls.data = r.json(parse_int=D, parse_float=D) | ||
43 | except (JSONDecodeError, SimpleJSONDecodeError): | ||
44 | cls.data = None | ||
45 | |||
46 | @classmethod | ||
47 | def parse_cryptoportfolio(cls, market, refetch=False): | ||
48 | if refetch or cls.data is None: | ||
49 | cls.get_cryptoportfolio(market) | ||
50 | |||
51 | def filter_weights(weight_hash): | ||
52 | if weight_hash[1][0] == 0: | ||
53 | return False | ||
54 | if weight_hash[0] == "_row": | ||
55 | return False | ||
56 | return True | ||
57 | |||
58 | def clean_weights(i): | ||
59 | def clean_weights_(h): | ||
60 | if h[0].endswith("s"): | ||
61 | return [h[0][0:-1], (h[1][i], "short")] | ||
62 | else: | ||
63 | return [h[0], (h[1][i], "long")] | ||
64 | return clean_weights_ | ||
65 | |||
66 | def parse_weights(portfolio_hash): | ||
67 | weights_hash = portfolio_hash["weights"] | ||
68 | weights = {} | ||
69 | for i in range(len(weights_hash["_row"])): | ||
70 | date = datetime.strptime(weights_hash["_row"][i], "%Y-%m-%d") | ||
71 | weights[date] = dict(filter( | ||
72 | filter_weights, | ||
73 | map(clean_weights(i), weights_hash.items()))) | ||
74 | return weights | ||
75 | |||
76 | high_liquidity = parse_weights(cls.data["portfolio_1"]) | ||
77 | medium_liquidity = parse_weights(cls.data["portfolio_2"]) | ||
78 | |||
79 | cls.liquidities = { | ||
80 | "medium": medium_liquidity, | ||
81 | "high": high_liquidity, | ||
82 | } | ||
83 | cls.last_date = max(max(medium_liquidity.keys()), max(high_liquidity.keys())) | ||
84 | |||
85 | class Computation: | 8 | class Computation: |
86 | computations = { | 9 | computations = { |
87 | "default": lambda x, y: x[y], | 10 | "default": lambda x, y: x[y], |
@@ -1,10 +1,14 @@ | |||
1 | import time | ||
2 | import requests | ||
1 | import portfolio | 3 | import portfolio |
2 | import simplejson as json | 4 | import simplejson as json |
3 | from decimal import Decimal as D, ROUND_DOWN | 5 | from decimal import Decimal as D, ROUND_DOWN |
4 | from datetime import date, datetime | 6 | from datetime import date, datetime, timedelta |
5 | import inspect | 7 | import inspect |
8 | from json import JSONDecodeError | ||
9 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | ||
6 | 10 | ||
7 | __all__ = ["BalanceStore", "ReportStore", "TradeStore"] | 11 | __all__ = ["Portfolio", "BalanceStore", "ReportStore", "TradeStore"] |
8 | 12 | ||
9 | class ReportStore: | 13 | class ReportStore: |
10 | def __init__(self, market, verbose_print=True): | 14 | def __init__(self, market, verbose_print=True): |
@@ -213,7 +217,7 @@ class BalanceStore: | |||
213 | 217 | ||
214 | def dispatch_assets(self, amount, liquidity="medium", repartition=None): | 218 | def dispatch_assets(self, amount, liquidity="medium", repartition=None): |
215 | if repartition is None: | 219 | if repartition is None: |
216 | repartition = portfolio.Portfolio.repartition(self.market, liquidity=liquidity) | 220 | repartition = Portfolio.repartition(liquidity=liquidity) |
217 | sum_ratio = sum([v[0] for k, v in repartition.items()]) | 221 | sum_ratio = sum([v[0] for k, v in repartition.items()]) |
218 | amounts = {} | 222 | amounts = {} |
219 | for currency, (ptt, trade_type) in repartition.items(): | 223 | for currency, (ptt, trade_type) in repartition.items(): |
@@ -301,4 +305,79 @@ class TradeStore: | |||
301 | for order in self.all_orders(state="open"): | 305 | for order in self.all_orders(state="open"): |
302 | order.get_status() | 306 | order.get_status() |
303 | 307 | ||
308 | class Portfolio: | ||
309 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" | ||
310 | liquidities = {} | ||
311 | data = None | ||
312 | last_date = None | ||
313 | report = ReportStore(None) | ||
314 | |||
315 | @classmethod | ||
316 | def wait_for_recent(cls, delta=4): | ||
317 | cls.get_cryptoportfolio() | ||
318 | while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): | ||
319 | time.sleep(30) | ||
320 | cls.report.print_log("Attempt to fetch up-to-date cryptoportfolio") | ||
321 | cls.get_cryptoportfolio(refetch=True) | ||
322 | |||
323 | @classmethod | ||
324 | def repartition(cls, liquidity="medium"): | ||
325 | cls.get_cryptoportfolio() | ||
326 | liquidities = cls.liquidities[liquidity] | ||
327 | return liquidities[cls.last_date] | ||
328 | |||
329 | @classmethod | ||
330 | def get_cryptoportfolio(cls, refetch=False): | ||
331 | if cls.data is not None and not refetch: | ||
332 | return | ||
333 | try: | ||
334 | r = requests.get(cls.URL) | ||
335 | cls.report.log_http_request(r.request.method, | ||
336 | r.request.url, r.request.body, r.request.headers, r) | ||
337 | except Exception as e: | ||
338 | cls.report.log_error("get_cryptoportfolio", exception=e) | ||
339 | return | ||
340 | try: | ||
341 | cls.data = r.json(parse_int=D, parse_float=D) | ||
342 | cls.parse_cryptoportfolio() | ||
343 | except (JSONDecodeError, SimpleJSONDecodeError): | ||
344 | cls.data = None | ||
345 | cls.liquidities = {} | ||
346 | |||
347 | @classmethod | ||
348 | def parse_cryptoportfolio(cls): | ||
349 | def filter_weights(weight_hash): | ||
350 | if weight_hash[1][0] == 0: | ||
351 | return False | ||
352 | if weight_hash[0] == "_row": | ||
353 | return False | ||
354 | return True | ||
355 | |||
356 | def clean_weights(i): | ||
357 | def clean_weights_(h): | ||
358 | if h[0].endswith("s"): | ||
359 | return [h[0][0:-1], (h[1][i], "short")] | ||
360 | else: | ||
361 | return [h[0], (h[1][i], "long")] | ||
362 | return clean_weights_ | ||
363 | |||
364 | def parse_weights(portfolio_hash): | ||
365 | weights_hash = portfolio_hash["weights"] | ||
366 | weights = {} | ||
367 | for i in range(len(weights_hash["_row"])): | ||
368 | date = datetime.strptime(weights_hash["_row"][i], "%Y-%m-%d") | ||
369 | weights[date] = dict(filter( | ||
370 | filter_weights, | ||
371 | map(clean_weights(i), weights_hash.items()))) | ||
372 | return weights | ||
373 | |||
374 | high_liquidity = parse_weights(cls.data["portfolio_1"]) | ||
375 | medium_liquidity = parse_weights(cls.data["portfolio_2"]) | ||
376 | |||
377 | cls.liquidities = { | ||
378 | "medium": medium_liquidity, | ||
379 | "high": high_liquidity, | ||
380 | } | ||
381 | cls.last_date = max(max(medium_liquidity.keys()), max(high_liquidity.keys())) | ||
382 | |||
304 | 383 | ||
@@ -7,7 +7,7 @@ from unittest import mock | |||
7 | import requests | 7 | import requests |
8 | import requests_mock | 8 | import requests_mock |
9 | from io import StringIO | 9 | from io import StringIO |
10 | import portfolio, market, main | 10 | import portfolio, market, main, store |
11 | 11 | ||
12 | limits = ["acceptance", "unit"] | 12 | limits = ["acceptance", "unit"] |
13 | for test_type in limits: | 13 | for test_type in limits: |
@@ -32,7 +32,11 @@ class WebMockTestCase(unittest.TestCase): | |||
32 | self.m.debug = False | 32 | self.m.debug = False |
33 | 33 | ||
34 | self.patchers = [ | 34 | self.patchers = [ |
35 | mock.patch.multiple(portfolio.Portfolio, last_date=None, data=None, liquidities={}), | 35 | mock.patch.multiple(market.Portfolio, |
36 | last_date=None, | ||
37 | data=None, | ||
38 | liquidities={}, | ||
39 | report=mock.Mock()), | ||
36 | mock.patch.multiple(portfolio.Computation, | 40 | mock.patch.multiple(portfolio.Computation, |
37 | computations=portfolio.Computation.computations), | 41 | computations=portfolio.Computation.computations), |
38 | ] | 42 | ] |
@@ -439,57 +443,64 @@ class poloniexETest(unittest.TestCase): | |||
439 | 443 | ||
440 | @unittest.skipUnless("unit" in limits, "Unit skipped") | 444 | @unittest.skipUnless("unit" in limits, "Unit skipped") |
441 | class PortfolioTest(WebMockTestCase): | 445 | class PortfolioTest(WebMockTestCase): |
442 | def fill_data(self): | ||
443 | if self.json_response is not None: | ||
444 | portfolio.Portfolio.data = self.json_response | ||
445 | |||
446 | def setUp(self): | 446 | def setUp(self): |
447 | super(PortfolioTest, self).setUp() | 447 | super(PortfolioTest, self).setUp() |
448 | 448 | ||
449 | with open("test_samples/test_portfolio.json") as example: | 449 | with open("test_samples/test_portfolio.json") as example: |
450 | self.json_response = example.read() | 450 | self.json_response = example.read() |
451 | 451 | ||
452 | self.wm.get(portfolio.Portfolio.URL, text=self.json_response) | 452 | self.wm.get(market.Portfolio.URL, text=self.json_response) |
453 | 453 | ||
454 | def test_get_cryptoportfolio(self): | 454 | @mock.patch.object(market.Portfolio, "parse_cryptoportfolio") |
455 | self.wm.get(portfolio.Portfolio.URL, [ | 455 | def test_get_cryptoportfolio(self, parse_cryptoportfolio): |
456 | self.wm.get(market.Portfolio.URL, [ | ||
456 | {"text":'{ "foo": "bar" }', "status_code": 200}, | 457 | {"text":'{ "foo": "bar" }', "status_code": 200}, |
457 | {"text": "System Error", "status_code": 500}, | 458 | {"text": "System Error", "status_code": 500}, |
458 | {"exc": requests.exceptions.ConnectTimeout}, | 459 | {"exc": requests.exceptions.ConnectTimeout}, |
459 | ]) | 460 | ]) |
460 | portfolio.Portfolio.get_cryptoportfolio(self.m) | 461 | market.Portfolio.get_cryptoportfolio() |
461 | self.assertIn("foo", portfolio.Portfolio.data) | 462 | self.assertIn("foo", market.Portfolio.data) |
462 | self.assertEqual("bar", portfolio.Portfolio.data["foo"]) | 463 | self.assertEqual("bar", market.Portfolio.data["foo"]) |
463 | self.assertTrue(self.wm.called) | 464 | self.assertTrue(self.wm.called) |
464 | self.assertEqual(1, self.wm.call_count) | 465 | self.assertEqual(1, self.wm.call_count) |
465 | self.m.report.log_error.assert_not_called() | 466 | market.Portfolio.report.log_error.assert_not_called() |
466 | self.m.report.log_http_request.assert_called_once() | 467 | market.Portfolio.report.log_http_request.assert_called_once() |
467 | self.m.report.log_http_request.reset_mock() | 468 | parse_cryptoportfolio.assert_called_once_with() |
468 | 469 | market.Portfolio.report.log_http_request.reset_mock() | |
469 | portfolio.Portfolio.get_cryptoportfolio(self.m) | 470 | parse_cryptoportfolio.reset_mock() |
470 | self.assertIsNone(portfolio.Portfolio.data) | 471 | market.Portfolio.data = None |
472 | |||
473 | market.Portfolio.get_cryptoportfolio() | ||
474 | self.assertIsNone(market.Portfolio.data) | ||
471 | self.assertEqual(2, self.wm.call_count) | 475 | self.assertEqual(2, self.wm.call_count) |
472 | self.m.report.log_error.assert_not_called() | 476 | parse_cryptoportfolio.assert_not_called() |
473 | self.m.report.log_http_request.assert_called_once() | 477 | market.Portfolio.report.log_error.assert_not_called() |
474 | self.m.report.log_http_request.reset_mock() | 478 | market.Portfolio.report.log_http_request.assert_called_once() |
475 | 479 | market.Portfolio.report.log_http_request.reset_mock() | |
480 | parse_cryptoportfolio.reset_mock() | ||
481 | |||
482 | market.Portfolio.data = "Foo" | ||
483 | market.Portfolio.get_cryptoportfolio() | ||
484 | self.assertEqual(2, self.wm.call_count) | ||
485 | parse_cryptoportfolio.assert_not_called() | ||
476 | 486 | ||
477 | portfolio.Portfolio.data = "Foo" | 487 | market.Portfolio.get_cryptoportfolio(refetch=True) |
478 | portfolio.Portfolio.get_cryptoportfolio(self.m) | 488 | self.assertEqual("Foo", market.Portfolio.data) |
479 | self.assertEqual("Foo", portfolio.Portfolio.data) | ||
480 | self.assertEqual(3, self.wm.call_count) | 489 | self.assertEqual(3, self.wm.call_count) |
481 | self.m.report.log_error.assert_called_once_with("get_cryptoportfolio", | 490 | market.Portfolio.report.log_error.assert_called_once_with("get_cryptoportfolio", |
482 | exception=mock.ANY) | 491 | exception=mock.ANY) |
483 | self.m.report.log_http_request.assert_not_called() | 492 | market.Portfolio.report.log_http_request.assert_not_called() |
484 | 493 | ||
485 | def test_parse_cryptoportfolio(self): | 494 | def test_parse_cryptoportfolio(self): |
486 | portfolio.Portfolio.parse_cryptoportfolio(self.m) | 495 | market.Portfolio.data = store.json.loads(self.json_response, parse_int=D, |
496 | parse_float=D) | ||
497 | market.Portfolio.parse_cryptoportfolio() | ||
487 | 498 | ||
488 | self.assertListEqual( | 499 | self.assertListEqual( |
489 | ["medium", "high"], | 500 | ["medium", "high"], |
490 | list(portfolio.Portfolio.liquidities.keys())) | 501 | list(market.Portfolio.liquidities.keys())) |
491 | 502 | ||
492 | liquidities = portfolio.Portfolio.liquidities | 503 | liquidities = market.Portfolio.liquidities |
493 | self.assertEqual(10, len(liquidities["medium"].keys())) | 504 | self.assertEqual(10, len(liquidities["medium"].keys())) |
494 | self.assertEqual(10, len(liquidities["high"].keys())) | 505 | self.assertEqual(10, len(liquidities["high"].keys())) |
495 | 506 | ||
@@ -517,94 +528,64 @@ class PortfolioTest(WebMockTestCase): | |||
517 | 'XCP': (D("0.1"), "long"), | 528 | 'XCP': (D("0.1"), "long"), |
518 | } | 529 | } |
519 | self.assertDictEqual(expected, liquidities["medium"][date]) | 530 | self.assertDictEqual(expected, liquidities["medium"][date]) |
520 | self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date) | 531 | self.assertEqual(portfolio.datetime(2018, 1, 15), market.Portfolio.last_date) |
521 | 532 | ||
522 | self.m.report.log_http_request.assert_called_once_with("GET", | 533 | @mock.patch.object(market.Portfolio, "get_cryptoportfolio") |
523 | portfolio.Portfolio.URL, None, mock.ANY, mock.ANY) | 534 | def test_repartition(self, get_cryptoportfolio): |
524 | self.m.report.log_http_request.reset_mock() | 535 | market.Portfolio.liquidities = { |
525 | 536 | "medium": { | |
526 | # It doesn't refetch the data when available | 537 | "2018-03-01": "medium_2018-03-01", |
527 | portfolio.Portfolio.parse_cryptoportfolio(self.m) | 538 | "2018-03-08": "medium_2018-03-08", |
528 | self.m.report.log_http_request.assert_not_called() | 539 | }, |
529 | 540 | "high": { | |
530 | self.assertEqual(1, self.wm.call_count) | 541 | "2018-03-01": "high_2018-03-01", |
531 | 542 | "2018-03-08": "high_2018-03-08", | |
532 | portfolio.Portfolio.parse_cryptoportfolio(self.m, refetch=True) | 543 | } |
533 | self.assertEqual(2, self.wm.call_count) | ||
534 | self.m.report.log_http_request.assert_called_once() | ||
535 | |||
536 | def test_repartition(self): | ||
537 | expected_medium = { | ||
538 | 'BTC': (D("1.1102e-16"), "long"), | ||
539 | 'USDT': (D("0.1"), "long"), | ||
540 | 'ETC': (D("0.1"), "long"), | ||
541 | 'FCT': (D("0.1"), "long"), | ||
542 | 'OMG': (D("0.1"), "long"), | ||
543 | 'STEEM': (D("0.1"), "long"), | ||
544 | 'STRAT': (D("0.1"), "long"), | ||
545 | 'XEM': (D("0.1"), "long"), | ||
546 | 'XMR': (D("0.1"), "long"), | ||
547 | 'XVC': (D("0.1"), "long"), | ||
548 | 'ZRX': (D("0.1"), "long"), | ||
549 | } | ||
550 | expected_high = { | ||
551 | 'USDT': (D("0.1226"), "long"), | ||
552 | 'BTC': (D("0.1429"), "long"), | ||
553 | 'ETC': (D("0.1127"), "long"), | ||
554 | 'ETH': (D("0.1569"), "long"), | ||
555 | 'FCT': (D("0.3341"), "long"), | ||
556 | 'GAS': (D("0.1308"), "long"), | ||
557 | } | 544 | } |
545 | market.Portfolio.last_date = "2018-03-08" | ||
558 | 546 | ||
559 | self.assertEqual(expected_medium, portfolio.Portfolio.repartition(self.m)) | 547 | self.assertEqual("medium_2018-03-08", market.Portfolio.repartition()) |
560 | self.assertEqual(expected_medium, portfolio.Portfolio.repartition(self.m, liquidity="medium")) | 548 | get_cryptoportfolio.assert_called_once_with() |
561 | self.assertEqual(expected_high, portfolio.Portfolio.repartition(self.m, liquidity="high")) | 549 | self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(liquidity="medium")) |
562 | 550 | self.assertEqual("high_2018-03-08", market.Portfolio.repartition(liquidity="high")) | |
563 | self.assertEqual(1, self.wm.call_count) | ||
564 | |||
565 | portfolio.Portfolio.repartition(self.m) | ||
566 | self.assertEqual(1, self.wm.call_count) | ||
567 | |||
568 | portfolio.Portfolio.repartition(self.m, refetch=True) | ||
569 | self.assertEqual(2, self.wm.call_count) | ||
570 | self.m.report.log_http_request.assert_called() | ||
571 | self.assertEqual(2, self.m.report.log_http_request.call_count) | ||
572 | 551 | ||
573 | @mock.patch.object(portfolio.time, "sleep") | 552 | @mock.patch.object(market.time, "sleep") |
574 | @mock.patch.object(portfolio.Portfolio, "repartition") | 553 | @mock.patch.object(market.Portfolio, "get_cryptoportfolio") |
575 | def test_wait_for_recent(self, repartition, sleep): | 554 | def test_wait_for_recent(self, get_cryptoportfolio, sleep): |
576 | self.call_count = 0 | 555 | self.call_count = 0 |
577 | def _repartition(market, refetch): | 556 | def _get(refetch=False): |
578 | self.assertEqual(self.m, market) | 557 | if self.call_count != 0: |
579 | self.assertTrue(refetch) | 558 | self.assertTrue(refetch) |
559 | else: | ||
560 | self.assertFalse(refetch) | ||
580 | self.call_count += 1 | 561 | self.call_count += 1 |
581 | portfolio.Portfolio.last_date = portfolio.datetime.now()\ | 562 | market.Portfolio.last_date = store.datetime.now()\ |
582 | - portfolio.timedelta(10)\ | 563 | - store.timedelta(10)\ |
583 | + portfolio.timedelta(self.call_count) | 564 | + store.timedelta(self.call_count) |
584 | repartition.side_effect = _repartition | 565 | get_cryptoportfolio.side_effect = _get |
585 | 566 | ||
586 | portfolio.Portfolio.wait_for_recent(self.m) | 567 | market.Portfolio.wait_for_recent() |
587 | sleep.assert_called_with(30) | 568 | sleep.assert_called_with(30) |
588 | self.assertEqual(6, sleep.call_count) | 569 | self.assertEqual(6, sleep.call_count) |
589 | self.assertEqual(7, repartition.call_count) | 570 | self.assertEqual(7, get_cryptoportfolio.call_count) |
590 | self.m.report.print_log.assert_called_with("Attempt to fetch up-to-date cryptoportfolio") | 571 | market.Portfolio.report.print_log.assert_called_with("Attempt to fetch up-to-date cryptoportfolio") |
591 | 572 | ||
592 | sleep.reset_mock() | 573 | sleep.reset_mock() |
593 | repartition.reset_mock() | 574 | get_cryptoportfolio.reset_mock() |
594 | portfolio.Portfolio.last_date = None | 575 | market.Portfolio.last_date = None |
595 | self.call_count = 0 | 576 | self.call_count = 0 |
596 | portfolio.Portfolio.wait_for_recent(self.m, delta=15) | 577 | market.Portfolio.wait_for_recent(delta=15) |
597 | sleep.assert_not_called() | 578 | sleep.assert_not_called() |
598 | self.assertEqual(1, repartition.call_count) | 579 | self.assertEqual(1, get_cryptoportfolio.call_count) |
599 | 580 | ||
600 | sleep.reset_mock() | 581 | sleep.reset_mock() |
601 | repartition.reset_mock() | 582 | get_cryptoportfolio.reset_mock() |
602 | portfolio.Portfolio.last_date = None | 583 | market.Portfolio.last_date = None |
603 | self.call_count = 0 | 584 | self.call_count = 0 |
604 | portfolio.Portfolio.wait_for_recent(self.m, delta=1) | 585 | market.Portfolio.wait_for_recent(delta=1) |
605 | sleep.assert_called_with(30) | 586 | sleep.assert_called_with(30) |
606 | self.assertEqual(9, sleep.call_count) | 587 | self.assertEqual(9, sleep.call_count) |
607 | self.assertEqual(10, repartition.call_count) | 588 | self.assertEqual(10, get_cryptoportfolio.call_count) |
608 | 589 | ||
609 | @unittest.skipUnless("unit" in limits, "Unit skipped") | 590 | @unittest.skipUnless("unit" in limits, "Unit skipped") |
610 | class AmountTest(WebMockTestCase): | 591 | class AmountTest(WebMockTestCase): |
@@ -1047,7 +1028,7 @@ class MarketTest(WebMockTestCase): | |||
1047 | self.assertEqual("Foo", m.fetch_fees()) | 1028 | self.assertEqual("Foo", m.fetch_fees()) |
1048 | self.ccxt.fetch_fees.assert_not_called() | 1029 | self.ccxt.fetch_fees.assert_not_called() |
1049 | 1030 | ||
1050 | @mock.patch.object(portfolio.Portfolio, "repartition") | 1031 | @mock.patch.object(market.Portfolio, "repartition") |
1051 | @mock.patch.object(market.Market, "get_ticker") | 1032 | @mock.patch.object(market.Market, "get_ticker") |
1052 | @mock.patch.object(market.TradeStore, "compute_trades") | 1033 | @mock.patch.object(market.TradeStore, "compute_trades") |
1053 | def test_prepare_trades(self, compute_trades, get_ticker, repartition): | 1034 | def test_prepare_trades(self, compute_trades, get_ticker, repartition): |
@@ -1098,7 +1079,7 @@ class MarketTest(WebMockTestCase): | |||
1098 | m.report.log_balances.assert_called_once_with(tag="tag") | 1079 | m.report.log_balances.assert_called_once_with(tag="tag") |
1099 | 1080 | ||
1100 | 1081 | ||
1101 | @mock.patch.object(portfolio.time, "sleep") | 1082 | @mock.patch.object(market.time, "sleep") |
1102 | @mock.patch.object(market.TradeStore, "all_orders") | 1083 | @mock.patch.object(market.TradeStore, "all_orders") |
1103 | def test_follow_orders(self, all_orders, time_mock): | 1084 | def test_follow_orders(self, all_orders, time_mock): |
1104 | for debug, sleep in [ | 1085 | for debug, sleep in [ |
@@ -1693,7 +1674,7 @@ class BalanceStoreTest(WebMockTestCase): | |||
1693 | self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies())) | 1674 | self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies())) |
1694 | self.m.report.log_balances.assert_called_with(tag="foo") | 1675 | self.m.report.log_balances.assert_called_with(tag="foo") |
1695 | 1676 | ||
1696 | @mock.patch.object(portfolio.Portfolio, "repartition") | 1677 | @mock.patch.object(market.Portfolio, "repartition") |
1697 | def test_dispatch_assets(self, repartition): | 1678 | def test_dispatch_assets(self, repartition): |
1698 | self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance | 1679 | self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance |
1699 | 1680 | ||
@@ -1710,7 +1691,7 @@ class BalanceStoreTest(WebMockTestCase): | |||
1710 | repartition.return_value = repartition_hash | 1691 | repartition.return_value = repartition_hash |
1711 | 1692 | ||
1712 | amounts = balance_store.dispatch_assets(portfolio.Amount("BTC", "11.1")) | 1693 | amounts = balance_store.dispatch_assets(portfolio.Amount("BTC", "11.1")) |
1713 | repartition.assert_called_with(self.m, liquidity="medium") | 1694 | repartition.assert_called_with(liquidity="medium") |
1714 | self.assertIn("XEM", balance_store.currencies()) | 1695 | self.assertIn("XEM", balance_store.currencies()) |
1715 | self.assertEqual(D("2.6"), amounts["BTC"].value) | 1696 | self.assertEqual(D("2.6"), amounts["BTC"].value) |
1716 | self.assertEqual(D("7.5"), amounts["XEM"].value) | 1697 | self.assertEqual(D("7.5"), amounts["XEM"].value) |
@@ -3505,7 +3486,7 @@ class ProcessorTest(WebMockTestCase): | |||
3505 | 3486 | ||
3506 | processor.run_action("wait_for_recent", "bar", "baz") | 3487 | processor.run_action("wait_for_recent", "bar", "baz") |
3507 | 3488 | ||
3508 | method_mock.assert_called_with(self.m, foo="bar") | 3489 | method_mock.assert_called_with(foo="bar") |
3509 | 3490 | ||
3510 | def test_select_step(self): | 3491 | def test_select_step(self): |
3511 | processor = market.Processor(self.m) | 3492 | processor = market.Processor(self.m) |
@@ -3547,7 +3528,7 @@ class ProcessorTest(WebMockTestCase): | |||
3547 | processor = market.Processor(m) | 3528 | processor = market.Processor(m) |
3548 | 3529 | ||
3549 | method, arguments = processor.method_arguments("wait_for_recent") | 3530 | method, arguments = processor.method_arguments("wait_for_recent") |
3550 | self.assertEqual(portfolio.Portfolio.wait_for_recent, method) | 3531 | self.assertEqual(market.Portfolio.wait_for_recent, method) |
3551 | self.assertEqual(["delta"], arguments) | 3532 | self.assertEqual(["delta"], arguments) |
3552 | 3533 | ||
3553 | method, arguments = processor.method_arguments("prepare_trades") | 3534 | method, arguments = processor.method_arguments("prepare_trades") |
@@ -3730,7 +3711,7 @@ class AcceptanceTest(WebMockTestCase): | |||
3730 | market = mock.Mock() | 3711 | market = mock.Mock() |
3731 | market.fetch_all_balances.return_value = fetch_balance | 3712 | market.fetch_all_balances.return_value = fetch_balance |
3732 | market.fetch_ticker.side_effect = fetch_ticker | 3713 | market.fetch_ticker.side_effect = fetch_ticker |
3733 | with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): | 3714 | with mock.patch.object(market.Portfolio, "repartition", return_value=repartition): |
3734 | # Action 1 | 3715 | # Action 1 |
3735 | helper.prepare_trades(market) | 3716 | helper.prepare_trades(market) |
3736 | 3717 | ||
@@ -3809,7 +3790,7 @@ class AcceptanceTest(WebMockTestCase): | |||
3809 | "amount": "10", "total": "1" | 3790 | "amount": "10", "total": "1" |
3810 | } | 3791 | } |
3811 | ] | 3792 | ] |
3812 | with mock.patch.object(portfolio.time, "sleep") as sleep: | 3793 | with mock.patch.object(market.time, "sleep") as sleep: |
3813 | # Action 4 | 3794 | # Action 4 |
3814 | helper.follow_orders(verbose=False) | 3795 | helper.follow_orders(verbose=False) |
3815 | 3796 | ||
@@ -3850,7 +3831,7 @@ class AcceptanceTest(WebMockTestCase): | |||
3850 | } | 3831 | } |
3851 | market.fetch_all_balances.return_value = fetch_balance | 3832 | market.fetch_all_balances.return_value = fetch_balance |
3852 | 3833 | ||
3853 | with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): | 3834 | with mock.patch.object(market.Portfolio, "repartition", return_value=repartition): |
3854 | # Action 5 | 3835 | # Action 5 |
3855 | helper.prepare_trades(market, only="acquire", compute_value="average") | 3836 | helper.prepare_trades(market, only="acquire", compute_value="average") |
3856 | 3837 | ||
@@ -3922,7 +3903,7 @@ class AcceptanceTest(WebMockTestCase): | |||
3922 | # TODO | 3903 | # TODO |
3923 | # portfolio.TradeStore.run_orders() | 3904 | # portfolio.TradeStore.run_orders() |
3924 | 3905 | ||
3925 | with mock.patch.object(portfolio.time, "sleep") as sleep: | 3906 | with mock.patch.object(market.time, "sleep") as sleep: |
3926 | # Action 8 | 3907 | # Action 8 |
3927 | helper.follow_orders(verbose=False) | 3908 | helper.follow_orders(verbose=False) |
3928 | 3909 | ||