aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-04-20 19:48:28 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-04-20 20:20:08 +0200
commitceb7fc4c9e76857fefbe1dfe3f4dd3830d065a6f (patch)
tree8c92a741b37418e32e4a9de85f4077a41b42a61c
parentad64ff17e3aa7d7af40a3724a8cd9f19ca045422 (diff)
downloadTrader-ceb7fc4c9e76857fefbe1dfe3f4dd3830d065a6f.tar.gz
Trader-ceb7fc4c9e76857fefbe1dfe3f4dd3830d065a6f.tar.zst
Trader-ceb7fc4c9e76857fefbe1dfe3f4dd3830d065a6f.zip
Use generic parser for market processing
-rw-r--r--main.py5
-rw-r--r--market.py52
-rw-r--r--tests/test_market.py112
3 files changed, 76 insertions, 93 deletions
diff --git a/main.py b/main.py
index 2c7b570..f465d8d 100644
--- a/main.py
+++ b/main.py
@@ -149,7 +149,10 @@ def parse_args(argv):
149 parser.add_argument("--db-database", default="cryptoportfolio", 149 parser.add_argument("--db-database", default="cryptoportfolio",
150 help="Database access to database (default: cryptoportfolio)") 150 help="Database access to database (default: cryptoportfolio)")
151 151
152 return parser.parse_args(argv) 152 parsed = parser.parse_args(argv)
153 if parsed.action is None:
154 parsed.action = ["sell_all"]
155 return parsed
153 156
154def process(market_config, market_id, user_id, args, pg_config): 157def process(market_config, market_id, user_id, args, pg_config):
155 try: 158 try:
diff --git a/market.py b/market.py
index 7a37cf6..4593eb1 100644
--- a/market.py
+++ b/market.py
@@ -76,17 +76,13 @@ class Market:
76 76
77 def process(self, actions, before=False, after=False): 77 def process(self, actions, before=False, after=False):
78 try: 78 try:
79 if len(actions or []) == 0: 79 for action in actions:
80 if before: 80 if bool(before) is bool(after):
81 self.processor.process("sell_all", steps="before") 81 self.processor.process(action, steps="all")
82 if after: 82 elif before:
83 self.processor.process("sell_all", steps="after") 83 self.processor.process(action, steps="before")
84 else: 84 elif after:
85 for action in actions: 85 self.processor.process(action, steps="after")
86 if hasattr(self, action):
87 getattr(self, action)()
88 else:
89 self.report.log_error("market_process", message="Unknown action {}".format(action))
90 except Exception as e: 86 except Exception as e:
91 self.report.log_error("market_process", exception=e) 87 self.report.log_error("market_process", exception=e)
92 finally: 88 finally:
@@ -212,16 +208,7 @@ class Market:
212 liquidity=liquidity, repartition=repartition) 208 liquidity=liquidity, repartition=repartition)
213 self.trades.compute_trades(values_in_base, new_repartition, only=only) 209 self.trades.compute_trades(values_in_base, new_repartition, only=only)
214 210
215 # Helpers 211 def print_tickers(self, base_currency="BTC"):
216 def print_orders(self, base_currency="BTC"):
217 self.report.log_stage("print_orders")
218 self.balances.fetch_balances(tag="print_orders")
219 self.prepare_trades(base_currency=base_currency, compute_value="average")
220 self.trades.prepare_orders(compute_value="average")
221
222 def print_balances(self, base_currency="BTC"):
223 self.report.log_stage("print_balances")
224 self.balances.fetch_balances()
225 if base_currency is not None: 212 if base_currency is not None:
226 self.report.print_log("total:") 213 self.report.print_log("total:")
227 self.report.print_log(sum(self.balances.in_currency(base_currency).values())) 214 self.report.print_log(sum(self.balances.in_currency(base_currency).values()))
@@ -237,12 +224,20 @@ class Processor:
237 "wait_for_recent": {}, 224 "wait_for_recent": {},
238 }, 225 },
239 ], 226 ],
227 "print_balances": [
228 {
229 "name": "print_balances",
230 "number": 1,
231 "fetch_balances": ["begin"],
232 "print_tickers": { "base_currency": "BTC" },
233 }
234 ],
240 "print_orders": [ 235 "print_orders": [
241 { 236 {
242 "name": "wait", 237 "name": "wait",
243 "number": 1, 238 "number": 1,
244 "before": False, 239 "before": True,
245 "after": True, 240 "after": False,
246 "wait_for_recent": {}, 241 "wait_for_recent": {},
247 }, 242 },
248 { 243 {
@@ -328,7 +323,7 @@ class Processor:
328 ordered_actions = [ 323 ordered_actions = [
329 "wait_for_recent", "prepare_trades", "prepare_orders", 324 "wait_for_recent", "prepare_trades", "prepare_orders",
330 "move_balances", "run_orders", "follow_orders", 325 "move_balances", "run_orders", "follow_orders",
331 "close_trades"] 326 "close_trades", "print_tickers"]
332 327
333 def __init__(self, market): 328 def __init__(self, market):
334 self.market = market 329 self.market = market
@@ -337,7 +332,7 @@ class Processor:
337 if step == "all": 332 if step == "all":
338 return scenario 333 return scenario
339 elif step == "before" or step == "after": 334 elif step == "before" or step == "after":
340 return list(filter(lambda x: step in x and x[step], scenario)) 335 return list(filter(lambda x: x.get(step, False), scenario))
341 elif type(step) == int: 336 elif type(step) == int:
342 return [scenario[step-1]] 337 return [scenario[step-1]]
343 elif type(step) == str: 338 elif type(step) == str:
@@ -345,7 +340,12 @@ class Processor:
345 else: 340 else:
346 raise TypeError("Unknown step {}".format(step)) 341 raise TypeError("Unknown step {}".format(step))
347 342
343 def can_process(self, scenario_name):
344 return scenario_name in self.scenarios
345
348 def process(self, scenario_name, steps="all", **kwargs): 346 def process(self, scenario_name, steps="all", **kwargs):
347 if not self.can_process(scenario_name):
348 raise TypeError("Unknown scenario {}".format(scenario_name))
349 scenario = self.scenarios[scenario_name] 349 scenario = self.scenarios[scenario_name]
350 selected_steps = [] 350 selected_steps = []
351 351
@@ -388,6 +388,8 @@ class Processor:
388 method = self.market.follow_orders 388 method = self.market.follow_orders
389 elif action == "close_trades": 389 elif action == "close_trades":
390 method = self.market.trades.close_trades 390 method = self.market.trades.close_trades
391 elif action == "print_tickers":
392 method = self.market.print_tickers
391 393
392 signature = inspect.getfullargspec(method) 394 signature = inspect.getfullargspec(method)
393 defaults = signature.defaults or [] 395 defaults = signature.defaults or []
diff --git a/tests/test_market.py b/tests/test_market.py
index 14b23b5..b41cd6a 100644
--- a/tests/test_market.py
+++ b/tests/test_market.py
@@ -607,21 +607,7 @@ class MarketTest(WebMockTestCase):
607 file_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) 607 file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
608 db_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) 608 db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
609 609
610 def test_print_orders(self): 610 def test_print_tickers(self):
611 m = market.Market(self.ccxt, self.market_args())
612 with mock.patch.object(m.report, "log_stage") as log_stage,\
613 mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
614 mock.patch.object(m, "prepare_trades") as prepare_trades,\
615 mock.patch.object(m.trades, "prepare_orders") as prepare_orders:
616 m.print_orders()
617
618 log_stage.assert_called_with("print_orders")
619 fetch_balances.assert_called_with(tag="print_orders")
620 prepare_trades.assert_called_with(base_currency="BTC",
621 compute_value="average")
622 prepare_orders.assert_called_with(compute_value="average")
623
624 def test_print_balances(self):
625 m = market.Market(self.ccxt, self.market_args()) 611 m = market.Market(self.ccxt, self.market_args())
626 612
627 with mock.patch.object(m.balances, "in_currency") as in_currency,\ 613 with mock.patch.object(m.balances, "in_currency") as in_currency,\
@@ -634,10 +620,8 @@ class MarketTest(WebMockTestCase):
634 "ETH": portfolio.Amount("BTC", "0.3"), 620 "ETH": portfolio.Amount("BTC", "0.3"),
635 } 621 }
636 622
637 m.print_balances() 623 m.print_tickers()
638 624
639 log_stage.assert_called_once_with("print_balances")
640 fetch_balances.assert_called_with()
641 print_log.assert_has_calls([ 625 print_log.assert_has_calls([
642 mock.call("total:"), 626 mock.call("total:"),
643 mock.call(portfolio.Amount("BTC", "0.95")), 627 mock.call(portfolio.Amount("BTC", "0.95")),
@@ -648,8 +632,8 @@ class MarketTest(WebMockTestCase):
648 @mock.patch("market.Market.store_report") 632 @mock.patch("market.Market.store_report")
649 def test_process(self, store_report, log_error, process): 633 def test_process(self, store_report, log_error, process):
650 m = market.Market(self.ccxt, self.market_args()) 634 m = market.Market(self.ccxt, self.market_args())
651 with self.subTest(before=False, after=False): 635 with self.subTest(actions=[], before=False, after=False):
652 m.process(None) 636 m.process([])
653 637
654 process.assert_not_called() 638 process.assert_not_called()
655 store_report.assert_called_once() 639 store_report.assert_called_once()
@@ -659,9 +643,9 @@ class MarketTest(WebMockTestCase):
659 log_error.reset_mock() 643 log_error.reset_mock()
660 store_report.reset_mock() 644 store_report.reset_mock()
661 with self.subTest(before=True, after=False): 645 with self.subTest(before=True, after=False):
662 m.process(None, before=True) 646 m.process(["foo"], before=True)
663 647
664 process.assert_called_once_with("sell_all", steps="before") 648 process.assert_called_once_with("foo", steps="before")
665 store_report.assert_called_once() 649 store_report.assert_called_once()
666 log_error.assert_not_called() 650 log_error.assert_not_called()
667 651
@@ -669,7 +653,7 @@ class MarketTest(WebMockTestCase):
669 log_error.reset_mock() 653 log_error.reset_mock()
670 store_report.reset_mock() 654 store_report.reset_mock()
671 with self.subTest(before=False, after=True): 655 with self.subTest(before=False, after=True):
672 m.process(None, after=True) 656 m.process(["sell_all"], after=True)
673 657
674 process.assert_called_once_with("sell_all", steps="after") 658 process.assert_called_once_with("sell_all", steps="after")
675 store_report.assert_called_once() 659 store_report.assert_called_once()
@@ -678,54 +662,30 @@ class MarketTest(WebMockTestCase):
678 process.reset_mock() 662 process.reset_mock()
679 log_error.reset_mock() 663 log_error.reset_mock()
680 store_report.reset_mock() 664 store_report.reset_mock()
681 with self.subTest(before=True, after=True): 665 with self.subTest(before=False, after=False):
682 m.process(None, before=True, after=True) 666 m.process(["foo"])
683 667
684 process.assert_has_calls([ 668 process.assert_called_once_with("foo", steps="all")
685 mock.call("sell_all", steps="before"),
686 mock.call("sell_all", steps="after"),
687 ])
688 store_report.assert_called_once() 669 store_report.assert_called_once()
689 log_error.assert_not_called() 670 log_error.assert_not_called()
690 671
691 process.reset_mock() 672 process.reset_mock()
692 log_error.reset_mock() 673 log_error.reset_mock()
693 store_report.reset_mock() 674 store_report.reset_mock()
694 with self.subTest(action="print_balances"),\ 675 with self.subTest(before=True, after=True):
695 mock.patch.object(m, "print_balances") as print_balances: 676 m.process(["sell_all"], before=True, after=True)
696 m.process(["print_balances"])
697 677
698 process.assert_not_called() 678 process.assert_called_once_with("sell_all", steps="all")
699 log_error.assert_not_called()
700 store_report.assert_called_once() 679 store_report.assert_called_once()
701 print_balances.assert_called_once_with()
702
703 log_error.reset_mock()
704 store_report.reset_mock()
705 with self.subTest(action="print_orders"),\
706 mock.patch.object(m, "print_orders") as print_orders,\
707 mock.patch.object(m, "print_balances") as print_balances:
708 m.process(["print_orders", "print_balances"])
709
710 process.assert_not_called()
711 log_error.assert_not_called() 680 log_error.assert_not_called()
712 store_report.assert_called_once()
713 print_orders.assert_called_once_with()
714 print_balances.assert_called_once_with()
715
716 log_error.reset_mock()
717 store_report.reset_mock()
718 with self.subTest(action="unknown"):
719 m.process(["unknown"])
720 log_error.assert_called_once_with("market_process", message="Unknown action unknown")
721 store_report.assert_called_once()
722 681
682 process.reset_mock()
723 log_error.reset_mock() 683 log_error.reset_mock()
724 store_report.reset_mock() 684 store_report.reset_mock()
725 with self.subTest(unhandled_exception=True): 685 with self.subTest(unhandled_exception=True):
726 process.side_effect = Exception("bouh") 686 process.side_effect = Exception("bouh")
727 687
728 m.process(None, before=True) 688 m.process(["some_action"], before=True)
729 log_error.assert_called_with("market_process", exception=mock.ANY) 689 log_error.assert_called_with("market_process", exception=mock.ANY)
730 store_report.assert_called_once() 690 store_report.assert_called_once()
731 691
@@ -768,24 +728,39 @@ class ProcessorTest(WebMockTestCase):
768 with self.assertRaises(TypeError): 728 with self.assertRaises(TypeError):
769 processor.select_steps(scenario, ["wait"]) 729 processor.select_steps(scenario, ["wait"])
770 730
731 def test_can_process(self):
732 processor = market.Processor(self.m)
733
734 with self.subTest(True):
735 self.assertTrue(processor.can_process("sell_all"))
736
737 with self.subTest(False):
738 self.assertFalse(processor.can_process("unknown_action"))
739
771 @mock.patch("market.Processor.process_step") 740 @mock.patch("market.Processor.process_step")
772 def test_process(self, process_step): 741 def test_process(self, process_step):
773 processor = market.Processor(self.m) 742 with self.subTest("unknown action"):
743 processor = market.Processor(self.m)
744 with self.assertRaises(TypeError):
745 processor.process("unknown_action")
746
747 with self.subTest("nominal case"):
748 processor = market.Processor(self.m)
774 749
775 processor.process("sell_all", foo="bar") 750 processor.process("sell_all", foo="bar")
776 self.assertEqual(3, process_step.call_count) 751 self.assertEqual(3, process_step.call_count)
777 752
778 steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls)) 753 steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls))
779 scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls)) 754 scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls))
780 kwargs = list(map(lambda x: x[1][2], process_step.mock_calls)) 755 kwargs = list(map(lambda x: x[1][2], process_step.mock_calls))
781 self.assertEqual(["all_sell", "wait", "all_buy"], steps) 756 self.assertEqual(["all_sell", "wait", "all_buy"], steps)
782 self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names) 757 self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names)
783 self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs) 758 self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs)
784 759
785 process_step.reset_mock() 760 process_step.reset_mock()
786 761
787 processor.process("sell_needed", steps=["before", "after"]) 762 processor.process("sell_needed", steps=["before", "after"])
788 self.assertEqual(3, process_step.call_count) 763 self.assertEqual(3, process_step.call_count)
789 764
790 def test_method_arguments(self): 765 def test_method_arguments(self):
791 ccxt = mock.Mock(spec=market.ccxt.poloniexE) 766 ccxt = mock.Mock(spec=market.ccxt.poloniexE)
@@ -816,6 +791,9 @@ class ProcessorTest(WebMockTestCase):
816 method, arguments = processor.method_arguments("close_trades") 791 method, arguments = processor.method_arguments("close_trades")
817 self.assertEqual(m.trades.close_trades, method) 792 self.assertEqual(m.trades.close_trades, method)
818 793
794 method, arguments = processor.method_arguments("print_tickers")
795 self.assertEqual(m.print_tickers, method)
796
819 def test_process_step(self): 797 def test_process_step(self):
820 processor = market.Processor(self.m) 798 processor = market.Processor(self.m)
821 799