aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-09-26 11:51:48 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-09-26 11:51:48 +0200
commit34865e65933da0db778e171fc787eca114ec562d (patch)
treeb5f513759e4ea98c06d2a61810533ce4bb609edf
parenta4bb44d8bc27af7c57f439ffd9edca51c2f0afb4 (diff)
parent512972fa1df14df4e208a1182096b1c51b5d38d1 (diff)
downloadTrader-34865e65933da0db778e171fc787eca114ec562d.tar.gz
Trader-34865e65933da0db778e171fc787eca114ec562d.tar.zst
Trader-34865e65933da0db778e171fc787eca114ec562d.zip
Merge branch 'dev'v1.10.2
-rw-r--r--market.py10
-rw-r--r--portfolio.py2
-rw-r--r--store.py13
-rw-r--r--tests/test_market.py34
-rw-r--r--tests/test_portfolio.py2
-rw-r--r--tests/test_store.py24
6 files changed, 59 insertions, 26 deletions
diff --git a/market.py b/market.py
index 546ee94..9612b17 100644
--- a/market.py
+++ b/market.py
@@ -427,16 +427,16 @@ class Processor:
427 self.market.report.log_stage("{}_begin".format(process_name)) 427 self.market.report.log_stage("{}_begin".format(process_name))
428 428
429 if "fetch_balances_begin" in step: 429 if "fetch_balances_begin" in step:
430 self.market.balances.fetch_balances(tag="{}_begin".format(process_name), 430 self.run_action("fetch_balances", step["fetch_balances_begin"],
431 **step["fetch_balances_begin"]) 431 dict(options, tag="{}_begin".format(process_name)))
432 432
433 for action in self.ordered_actions: 433 for action in self.ordered_actions:
434 if action in step: 434 if action in step:
435 self.run_action(action, step[action], options) 435 self.run_action(action, step[action], options)
436 436
437 if "fetch_balances_end" in step: 437 if "fetch_balances_end" in step:
438 self.market.balances.fetch_balances(tag="{}_end".format(process_name), 438 self.run_action("fetch_balances", step["fetch_balances_end"],
439 **step["fetch_balances_end"]) 439 dict(options, tag="{}_end".format(process_name)))
440 440
441 self.market.report.log_stage("{}_end".format(process_name)) 441 self.market.report.log_stage("{}_end".format(process_name))
442 442
@@ -459,6 +459,8 @@ class Processor:
459 method = self.market.trades.close_trades 459 method = self.market.trades.close_trades
460 elif action == "print_tickers": 460 elif action == "print_tickers":
461 method = self.market.print_tickers 461 method = self.market.print_tickers
462 elif action == "fetch_balances":
463 method = self.market.balances.fetch_balances
462 464
463 signature = inspect.getfullargspec(method) 465 signature = inspect.getfullargspec(method)
464 defaults = signature.defaults or [] 466 defaults = signature.defaults or []
diff --git a/portfolio.py b/portfolio.py
index 94472a3..1d29106 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -52,7 +52,7 @@ class Amount:
52 ticker=asset_ticker, 52 ticker=asset_ticker,
53 rate=rate) 53 rate=rate)
54 else: 54 else:
55 raise Exception("This asset is not available in the chosen market") 55 return Amount(other_currency, 0, linked_to=self, ticker=None, rate=0)
56 56
57 def as_json(self): 57 def as_json(self):
58 return { 58 return {
diff --git a/store.py b/store.py
index f987bb9..425f08d 100644
--- a/store.py
+++ b/store.py
@@ -304,7 +304,7 @@ class BalanceStore:
304 compute_value, type) 304 compute_value, type)
305 return amounts 305 return amounts
306 306
307 def fetch_balances(self, tag=None, add_portfolio=False, 307 def fetch_balances(self, tag=None, add_portfolio=False, liquidity="medium",
308 checkpoint=None, log_tickers=False, add_usdt=False, 308 checkpoint=None, log_tickers=False, add_usdt=False,
309 ticker_currency="BTC", ticker_compute_value="average", ticker_type="total"): 309 ticker_currency="BTC", ticker_compute_value="average", ticker_type="total"):
310 all_balances = self.market.ccxt.fetch_all_balances() 310 all_balances = self.market.ccxt.fetch_all_balances()
@@ -313,7 +313,7 @@ class BalanceStore:
313 currency in self.all: 313 currency in self.all:
314 self.all[currency] = portfolio.Balance(currency, balance) 314 self.all[currency] = portfolio.Balance(currency, balance)
315 if add_portfolio: 315 if add_portfolio:
316 for currency in Portfolio.repartition(from_cache=True): 316 for currency in Portfolio.repartition(from_cache=True, liquidity=liquidity):
317 self.all.setdefault(currency, portfolio.Balance(currency, {})) 317 self.all.setdefault(currency, portfolio.Balance(currency, {}))
318 if add_usdt: 318 if add_usdt:
319 self.all.setdefault("USDT", portfolio.Balance("USDT", {})) 319 self.all.setdefault("USDT", portfolio.Balance("USDT", {}))
@@ -517,7 +517,7 @@ class Portfolio:
517 elif delta < datetime.timedelta(days=1): 517 elif delta < datetime.timedelta(days=1):
518 return 60*60 518 return 60*60
519 else: 519 else:
520 raise "Too long waiting" 520 raise Exception("Too long waiting")
521 521
522 @classmethod 522 @classmethod
523 def start_worker(cls): 523 def start_worker(cls):
@@ -555,7 +555,10 @@ class Portfolio:
555 cls.report.print_log("[Worker] Fetching cryptoportfolio") 555 cls.report.print_log("[Worker] Fetching cryptoportfolio")
556 cls.get_cryptoportfolio(refetch=True) 556 cls.get_cryptoportfolio(refetch=True)
557 cls.callback.set() 557 cls.callback.set()
558 time.sleep(cls.next_wait_time()) 558 try:
559 time.sleep(cls.next_wait_time())
560 except Exception:
561 cls.stop_worker()
559 562
560 @classmethod 563 @classmethod
561 def stop_worker(cls): 564 def stop_worker(cls):
@@ -591,6 +594,8 @@ class Portfolio:
591 if cls.data.get() is not None and not refetch: 594 if cls.data.get() is not None and not refetch:
592 return 595 return
593 if cls.worker is not None and not cls.is_worker_thread(): 596 if cls.worker is not None and not cls.is_worker_thread():
597 if not cls.worker_started:
598 raise Exception("Portfolio worker is down and no usable data is present")
594 cls.notify_and_wait() 599 cls.notify_and_wait()
595 return 600 return
596 try: 601 try:
diff --git a/tests/test_market.py b/tests/test_market.py
index e6e6f36..9316480 100644
--- a/tests/test_market.py
+++ b/tests/test_market.py
@@ -1057,6 +1057,9 @@ class ProcessorTest(WebMockTestCase):
1057 method, arguments = processor.method_arguments("print_tickers") 1057 method, arguments = processor.method_arguments("print_tickers")
1058 self.assertEqual(m.print_tickers, method) 1058 self.assertEqual(m.print_tickers, method)
1059 1059
1060 method, arguments = processor.method_arguments("fetch_balances")
1061 self.assertEqual(m.balances.fetch_balances, method)
1062
1060 def test_process_step(self): 1063 def test_process_step(self):
1061 processor = market.Processor(self.m) 1064 processor = market.Processor(self.m)
1062 1065
@@ -1069,19 +1072,17 @@ class ProcessorTest(WebMockTestCase):
1069 mock.call("process_foo__2_sell_begin"), 1072 mock.call("process_foo__2_sell_begin"),
1070 mock.call("process_foo__2_sell_end"), 1073 mock.call("process_foo__2_sell_end"),
1071 ]) 1074 ])
1072 self.m.balances.fetch_balances.assert_has_calls([
1073 mock.call(tag="process_foo__2_sell_begin"),
1074 mock.call(tag="process_foo__2_sell_end"),
1075 ])
1076 1075
1077 self.assertEqual(5, run_action.call_count) 1076 self.assertEqual(7, run_action.call_count)
1078 1077
1079 run_action.assert_has_calls([ 1078 run_action.assert_has_calls([
1079 mock.call('fetch_balances', {}, {'foo': 'bar', 'tag': 'process_foo__2_sell_begin'}),
1080 mock.call('prepare_trades', {}, {'foo': 'bar'}), 1080 mock.call('prepare_trades', {}, {'foo': 'bar'}),
1081 mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}), 1081 mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}),
1082 mock.call('run_orders', {}, {'foo': 'bar'}), 1082 mock.call('run_orders', {}, {'foo': 'bar'}),
1083 mock.call('follow_orders', {}, {'foo': 'bar'}), 1083 mock.call('follow_orders', {}, {'foo': 'bar'}),
1084 mock.call('close_trades', {}, {'foo': 'bar'}), 1084 mock.call('close_trades', {}, {'foo': 'bar'}),
1085 mock.call('fetch_balances', {}, {'foo': 'bar', 'tag': 'process_foo__2_sell_end'}),
1085 ]) 1086 ])
1086 1087
1087 self.m.reset_mock() 1088 self.m.reset_mock()
@@ -1094,30 +1095,31 @@ class ProcessorTest(WebMockTestCase):
1094 mock.call("process_foo__0_print_balances_begin"), 1095 mock.call("process_foo__0_print_balances_begin"),
1095 mock.call("process_foo__0_print_balances_end"), 1096 mock.call("process_foo__0_print_balances_end"),
1096 ]) 1097 ])
1097 self.m.balances.fetch_balances.assert_has_calls([
1098 mock.call(add_portfolio=True, checkpoint='end',
1099 log_tickers=True,
1100 add_usdt=True,
1101 tag='process_foo__0_print_balances_begin')
1102 ])
1103 1098
1104 self.assertEqual(0, run_action.call_count) 1099 self.assertEqual(1, run_action.call_count)
1100 run_action.assert_has_calls([
1101 mock.call('fetch_balances',
1102 {'checkpoint': 'end', 'log_tickers': True, 'add_usdt': True, 'add_portfolio': True},
1103 {'foo': 'bar', 'tag': 'process_foo__0_print_balances_begin'}),
1104 ])
1105 1105
1106 self.m.reset_mock() 1106 self.m.reset_mock()
1107 with mock.patch.object(processor, "run_action") as run_action: 1107 with mock.patch.object(processor, "run_action") as run_action:
1108 step = processor.scenarios["sell_needed"][1] 1108 step = processor.scenarios["sell_needed"][1]
1109 1109
1110 processor.process_step("foo", step, {"foo":"bar"}) 1110 processor.process_step("foo", step, {"foo":"bar"})
1111 self.m.balances.fetch_balances.assert_not_called() 1111 self.assertEqual(1, run_action.call_count)
1112 1112
1113 self.m.reset_mock() 1113 self.m.reset_mock()
1114 with mock.patch.object(processor, "run_action") as run_action: 1114 with mock.patch.object(processor, "run_action") as run_action:
1115 step = processor.scenarios["print_balances"][0] 1115 step = processor.scenarios["print_balances"][0]
1116 1116
1117 processor.process_step("foo", step, {"foo":"bar"}) 1117 processor.process_step("foo", step, {"foo":"bar"})
1118 self.m.balances.fetch_balances.assert_called_once_with( 1118 run_action.assert_has_calls([
1119 add_portfolio=True, add_usdt=True, log_tickers=True, 1119 mock.call('fetch_balances',
1120 tag='process_foo__1_print_balances_begin') 1120 {'log_tickers': True, 'add_usdt': True, 'add_portfolio': True},
1121 {'foo': 'bar', 'tag': 'process_foo__1_print_balances_begin'}),
1122 ])
1121 1123
1122 def test_parse_args(self): 1124 def test_parse_args(self):
1123 processor = market.Processor(self.m) 1125 processor = market.Processor(self.m)
diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py
index d4e5ab9..cad3095 100644
--- a/tests/test_portfolio.py
+++ b/tests/test_portfolio.py
@@ -1856,7 +1856,7 @@ class AmountTest(WebMockTestCase):
1856 with self.subTest(desc="no ticker for currency"): 1856 with self.subTest(desc="no ticker for currency"):
1857 self.m.get_ticker.return_value = None 1857 self.m.get_ticker.return_value = None
1858 1858
1859 self.assertRaises(Exception, amount.in_currency, "ETH", self.m) 1859 self.assertEqual(portfolio.Amount("ETH", 0), amount.in_currency("ETH", self.m))
1860 1860
1861 with self.subTest(desc="nominal case"): 1861 with self.subTest(desc="nominal case"):
1862 self.m.get_ticker.return_value = { 1862 self.m.get_ticker.return_value = {
diff --git a/tests/test_store.py b/tests/test_store.py
index 4ab9bdf..6f220c8 100644
--- a/tests/test_store.py
+++ b/tests/test_store.py
@@ -1379,11 +1379,19 @@ class PortfolioTest(WebMockTestCase):
1379 with self.subTest(worker=False): 1379 with self.subTest(worker=False):
1380 market.Portfolio.data = store.LockedVar(None) 1380 market.Portfolio.data = store.LockedVar(None)
1381 market.Portfolio.worker = mock.Mock() 1381 market.Portfolio.worker = mock.Mock()
1382 market.Portfolio.worker_started = True
1382 is_worker.return_value = False 1383 is_worker.return_value = False
1383 market.Portfolio.get_cryptoportfolio() 1384 market.Portfolio.get_cryptoportfolio()
1384 notify.assert_called_once_with() 1385 notify.assert_called_once_with()
1385 parse_cryptoportfolio.assert_not_called() 1386 parse_cryptoportfolio.assert_not_called()
1386 store_cryptoportfolio.assert_not_called() 1387 store_cryptoportfolio.assert_not_called()
1388 with self.subTest(worker_started=False):
1389 market.Portfolio.data = store.LockedVar(None)
1390 market.Portfolio.worker = mock.Mock()
1391 market.Portfolio.worker_started = False
1392 is_worker.return_value = False
1393 with self.assertRaises(Exception):
1394 market.Portfolio.get_cryptoportfolio()
1387 1395
1388 def test_parse_cryptoportfolio(self): 1396 def test_parse_cryptoportfolio(self):
1389 with self.subTest(description="Normal case"): 1397 with self.subTest(description="Normal case"):
@@ -1649,6 +1657,22 @@ class PortfolioTest(WebMockTestCase):
1649 store.Portfolio.worker.join() 1657 store.Portfolio.worker.join()
1650 self.assertFalse(store.Portfolio.worker.is_alive()) 1658 self.assertFalse(store.Portfolio.worker.is_alive())
1651 1659
1660 with self.subTest("overdue"),\
1661 mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\
1662 mock.patch.object(store.Portfolio, "report") as report,\
1663 mock.patch.object(store.Portfolio, "next_wait_time") as wait,\
1664 mock.patch.object(store.time, "sleep") as sleep:
1665 wait.side_effect = Exception("Time over")
1666 store.Portfolio.start_worker()
1667
1668 store.Portfolio.worker_notify.set()
1669
1670 store.Portfolio.callback.wait()
1671
1672 report.print_log.assert_called_once_with("[Worker] Fetching cryptoportfolio")
1673 get.assert_called_once_with(refetch=True)
1674 self.assertFalse(store.Portfolio.worker.is_alive())
1675
1652 def test_notify_and_wait(self): 1676 def test_notify_and_wait(self):
1653 with mock.patch.object(store.Portfolio, "callback") as callback,\ 1677 with mock.patch.object(store.Portfolio, "callback") as callback,\
1654 mock.patch.object(store.Portfolio, "worker_notify") as worker_notify: 1678 mock.patch.object(store.Portfolio, "worker_notify") as worker_notify: