X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=tests%2Ftest_market.py;h=6a3322c579d43815fb15b8da538063427936a814;hb=9eb0de20f243bb78de0bf9118289f01f1ea1f77c;hp=14b23b5db968c6c886209aead82d13b847a067ac;hpb=ad64ff17e3aa7d7af40a3724a8cd9f19ca045422;p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FCryptoportfolio%2FTrader.git diff --git a/tests/test_market.py b/tests/test_market.py index 14b23b5..6a3322c 100644 --- a/tests/test_market.py +++ b/tests/test_market.py @@ -130,19 +130,20 @@ class MarketTest(WebMockTestCase): @mock.patch.object(market.Market, "get_ticker") @mock.patch.object(market.TradeStore, "compute_trades") def test_prepare_trades(self, compute_trades, get_ticker, repartition): - repartition.return_value = { - "XEM": (D("0.75"), "long"), - "BTC": (D("0.25"), "long"), - } - def _get_ticker(c1, c2): - if c1 == "USDT" and c2 == "BTC": - return { "average": D("0.0001") } - if c1 == "XVG" and c2 == "BTC": - return { "average": D("0.000001") } - self.fail("Should be called with {}, {}".format(c1, c2)) - get_ticker.side_effect = _get_ticker - - with mock.patch("market.ReportStore"): + with self.subTest(available_balance_only=False),\ + mock.patch("market.ReportStore"): + def _get_ticker(c1, c2): + if c1 == "USDT" and c2 == "BTC": + return { "average": D("0.0001") } + if c1 == "XVG" and c2 == "BTC": + return { "average": D("0.000001") } + self.fail("Should not be called with {}, {}".format(c1, c2)) + get_ticker.side_effect = _get_ticker + + repartition.return_value = { + "XEM": (D("0.75"), "long"), + "BTC": (D("0.25"), "long"), + } m = market.Market(self.ccxt, self.market_args()) self.ccxt.fetch_all_balances.return_value = { "USDT": { @@ -171,9 +172,109 @@ class MarketTest(WebMockTestCase): self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) m.report.log_stage.assert_called_once_with("prepare_trades", base_currency='BTC', compute_value='average', - liquidity='medium', only=None, repartition=None) + available_balance_only=False, liquidity='medium', + only=None, repartition=None) m.report.log_balances.assert_called_once_with(tag="tag") + compute_trades.reset_mock() + with self.subTest(available_balance_only=True),\ + mock.patch("market.ReportStore"): + def _get_ticker(c1, c2): + if c1 == "ZRC" and c2 == "BTC": + return { "average": D("0.0001") } + if c1 == "DOGE" and c2 == "BTC": + return { "average": D("0.000001") } + if c1 == "ETH" and c2 == "BTC": + return { "average": D("0.1") } + self.fail("Should not be called with {}, {}".format(c1, c2)) + get_ticker.side_effect = _get_ticker + + repartition.return_value = { + "DOGE": (D("0.25"), "short"), + "BTC": (D("0.25"), "long"), + "ETH": (D("0.25"), "long"), + "XMR": (D("0.25"), "long"), + } + m = market.Market(self.ccxt, self.market_args()) + self.ccxt.fetch_all_balances.return_value = { + "ZRC": { + "exchange_free": D("2.0"), + "exchange_used": D("0.0"), + "exchange_total": D("2.0"), + "total": D("2.0") + }, + "DOGE": { + "exchange_free": D("5.0"), + "exchange_used": D("0.0"), + "exchange_total": D("5.0"), + "total": D("5.0") + }, + "BTC": { + "exchange_free": D("0.075"), + "exchange_used": D("0.02"), + "exchange_total": D("0.095"), + "margin_available": D("0.025"), + "margin_in_position": D("0.01"), + "margin_total": D("0.035"), + "total": D("0.13") + }, + "ETH": { + "exchange_free": D("1.0"), + "exchange_used": D("0.0"), + "exchange_total": D("1.0"), + "total": D("1.0") + }, + } + + m.balances.fetch_balances(tag="tag") + m.prepare_trades(available_balance_only=True) + compute_trades.assert_called_once() + + call = compute_trades.call_args[0] + values_in_base = call[0] + new_repartition = call[1] + + self.assertEqual(portfolio.Amount("BTC", "-0.025"), + new_repartition["DOGE"] - values_in_base["DOGE"]) + self.assertEqual(portfolio.Amount("BTC", "0.025"), + new_repartition["ETH"] - values_in_base["ETH"]) + self.assertEqual(0, + new_repartition["ZRC"] - values_in_base["ZRC"]) + self.assertEqual(portfolio.Amount("BTC", "0.025"), + new_repartition["XMR"]) + + compute_trades.reset_mock() + with self.subTest(available_balance_only=True, balance=0),\ + mock.patch("market.ReportStore"): + def _get_ticker(c1, c2): + if c1 == "ETH" and c2 == "BTC": + return { "average": D("0.1") } + self.fail("Should not be called with {}, {}".format(c1, c2)) + get_ticker.side_effect = _get_ticker + + repartition.return_value = { + "BTC": (D("0.5"), "long"), + "ETH": (D("0.5"), "long"), + } + m = market.Market(self.ccxt, self.market_args()) + self.ccxt.fetch_all_balances.return_value = { + "ETH": { + "exchange_free": D("1.0"), + "exchange_used": D("0.0"), + "exchange_total": D("1.0"), + "total": D("1.0") + }, + } + + m.balances.fetch_balances(tag="tag") + m.prepare_trades(available_balance_only=True) + compute_trades.assert_called_once() + + call = compute_trades.call_args[0] + values_in_base = call[0] + new_repartition = call[1] + + self.assertEqual(new_repartition["ETH"], values_in_base["ETH"]) @mock.patch.object(market.time, "sleep") @mock.patch.object(market.TradeStore, "all_orders") @@ -530,23 +631,56 @@ class MarketTest(WebMockTestCase): m.store_database_report(datetime.datetime(2018, 3, 24)) self.assertEqual(stdout_mock.getvalue(), "impossible to store report to database: Exception; Bouh\n") + @mock.patch.object(market, "redis") + def test_store_redis_report(self, redis): + connect_mock = mock.Mock() + redis.Redis.return_value = connect_mock + + m = market.Market(self.ccxt, self.market_args(), + redis_config={"config": "redis_config"}, market_id=1) + + with self.subTest(error=False),\ + mock.patch.object(m, "report") as report: + report.to_json_redis.return_value = [ + ("type1", "payload1"), + ("type2", "payload2"), + ] + m.store_redis_report(datetime.datetime(2018, 3, 24)) + connect_mock.assert_has_calls([ + mock.call.set("/cryptoportfolio/1/2018-03-24T00:00:00/type1", "payload1", ex=31*24*60*60), + mock.call.set("/cryptoportfolio/1/latest/type1", "payload1"), + mock.call.set("/cryptoportfolio/1/2018-03-24T00:00:00/type2", "payload2", ex=31*24*60*60), + mock.call.set("/cryptoportfolio/1/latest/type2", "payload2"), + mock.call.set("/cryptoportfolio/1/latest/date", "2018-03-24T00:00:00"), + ]) + + connect_mock.reset_mock() + with self.subTest(error=True),\ + mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: + redis.Redis.side_effect = Exception("Bouh") + m.store_redis_report(datetime.datetime(2018, 3, 24)) + self.assertEqual(stdout_mock.getvalue(), "impossible to store report to redis: Exception; Bouh\n") + def test_store_report(self): m = market.Market(self.ccxt, self.market_args(report_db=False), user_id=1) with self.subTest(file=None, pg_config=None),\ mock.patch.object(m, "report") as report,\ mock.patch.object(m, "store_database_report") as db_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ mock.patch.object(m, "store_file_report") as file_report: m.store_report() report.merge.assert_called_with(store.Portfolio.report) file_report.assert_not_called() db_report.assert_not_called() + redis_report.assert_not_called() report.reset_mock() m = market.Market(self.ccxt, self.market_args(report_db=False, report_path="present"), user_id=1) with self.subTest(file="present", pg_config=None),\ mock.patch.object(m, "report") as report,\ mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ mock.patch.object(m, "store_database_report") as db_report,\ mock.patch.object(market.datetime, "datetime") as time_mock: @@ -557,12 +691,14 @@ class MarketTest(WebMockTestCase): report.merge.assert_called_with(store.Portfolio.report) file_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) db_report.assert_not_called() + redis_report.assert_not_called() report.reset_mock() m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"), user_id=1) with self.subTest(file="present", pg_config=None, report_db=True),\ mock.patch.object(m, "report") as report,\ mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ mock.patch.object(m, "store_database_report") as db_report,\ mock.patch.object(market.datetime, "datetime") as time_mock: @@ -573,12 +709,14 @@ class MarketTest(WebMockTestCase): report.merge.assert_called_with(store.Portfolio.report) file_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) db_report.assert_not_called() + redis_report.assert_not_called() report.reset_mock() m = market.Market(self.ccxt, self.market_args(report_db=True), pg_config="present", user_id=1) with self.subTest(file=None, pg_config="present"),\ mock.patch.object(m, "report") as report,\ mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ mock.patch.object(m, "store_database_report") as db_report,\ mock.patch.object(market.datetime, "datetime") as time_mock: @@ -589,6 +727,7 @@ class MarketTest(WebMockTestCase): report.merge.assert_called_with(store.Portfolio.report) file_report.assert_not_called() db_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) + redis_report.assert_not_called() report.reset_mock() m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"), @@ -596,6 +735,7 @@ class MarketTest(WebMockTestCase): with self.subTest(file="present", pg_config="present"),\ mock.patch.object(m, "report") as report,\ mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ mock.patch.object(m, "store_database_report") as db_report,\ mock.patch.object(market.datetime, "datetime") as time_mock: @@ -606,22 +746,54 @@ class MarketTest(WebMockTestCase): report.merge.assert_called_with(store.Portfolio.report) file_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) db_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) + redis_report.assert_not_called() - def test_print_orders(self): - m = market.Market(self.ccxt, self.market_args()) - with mock.patch.object(m.report, "log_stage") as log_stage,\ - mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\ - mock.patch.object(m, "prepare_trades") as prepare_trades,\ - mock.patch.object(m.trades, "prepare_orders") as prepare_orders: - m.print_orders() + report.reset_mock() + m = market.Market(self.ccxt, self.market_args(report_redis=False), + redis_config="redis_config", user_id=1) + with self.subTest(redis_config="present", report_redis=False),\ + mock.patch.object(m, "report") as report,\ + mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ + mock.patch.object(m, "store_database_report") as db_report,\ + mock.patch.object(market.datetime, "datetime") as time_mock: + + time_mock.now.return_value = datetime.datetime(2018, 2, 25) - log_stage.assert_called_with("print_orders") - fetch_balances.assert_called_with(tag="print_orders") - prepare_trades.assert_called_with(base_currency="BTC", - compute_value="average") - prepare_orders.assert_called_with(compute_value="average") + m.store_report() + redis_report.assert_not_called() - def test_print_balances(self): + report.reset_mock() + m = market.Market(self.ccxt, self.market_args(report_redis=True), + user_id=1) + with self.subTest(redis_config="absent", report_redis=True),\ + mock.patch.object(m, "report") as report,\ + mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ + mock.patch.object(m, "store_database_report") as db_report,\ + mock.patch.object(market.datetime, "datetime") as time_mock: + + time_mock.now.return_value = datetime.datetime(2018, 2, 25) + + m.store_report() + redis_report.assert_not_called() + + report.reset_mock() + m = market.Market(self.ccxt, self.market_args(report_redis=True), + redis_config="redis_config", user_id=1) + with self.subTest(redis_config="present", report_redis=True),\ + mock.patch.object(m, "report") as report,\ + mock.patch.object(m, "store_file_report") as file_report,\ + mock.patch.object(m, "store_redis_report") as redis_report,\ + mock.patch.object(m, "store_database_report") as db_report,\ + mock.patch.object(market.datetime, "datetime") as time_mock: + + time_mock.now.return_value = datetime.datetime(2018, 2, 25) + + m.store_report() + redis_report.assert_called_once_with(datetime.datetime(2018, 2, 25)) + + def test_print_tickers(self): m = market.Market(self.ccxt, self.market_args()) with mock.patch.object(m.balances, "in_currency") as in_currency,\ @@ -634,10 +806,8 @@ class MarketTest(WebMockTestCase): "ETH": portfolio.Amount("BTC", "0.3"), } - m.print_balances() + m.print_tickers() - log_stage.assert_called_once_with("print_balances") - fetch_balances.assert_called_with() print_log.assert_has_calls([ mock.call("total:"), mock.call(portfolio.Amount("BTC", "0.95")), @@ -648,8 +818,8 @@ class MarketTest(WebMockTestCase): @mock.patch("market.Market.store_report") def test_process(self, store_report, log_error, process): m = market.Market(self.ccxt, self.market_args()) - with self.subTest(before=False, after=False): - m.process(None) + with self.subTest(actions=[], before=False, after=False): + m.process([]) process.assert_not_called() store_report.assert_called_once() @@ -659,9 +829,9 @@ class MarketTest(WebMockTestCase): log_error.reset_mock() store_report.reset_mock() with self.subTest(before=True, after=False): - m.process(None, before=True) + m.process(["foo"], before=True) - process.assert_called_once_with("sell_all", steps="before") + process.assert_called_once_with("foo", steps="before") store_report.assert_called_once() log_error.assert_not_called() @@ -669,7 +839,7 @@ class MarketTest(WebMockTestCase): log_error.reset_mock() store_report.reset_mock() with self.subTest(before=False, after=True): - m.process(None, after=True) + m.process(["sell_all"], after=True) process.assert_called_once_with("sell_all", steps="after") store_report.assert_called_once() @@ -678,54 +848,30 @@ class MarketTest(WebMockTestCase): process.reset_mock() log_error.reset_mock() store_report.reset_mock() - with self.subTest(before=True, after=True): - m.process(None, before=True, after=True) + with self.subTest(before=False, after=False): + m.process(["foo"]) - process.assert_has_calls([ - mock.call("sell_all", steps="before"), - mock.call("sell_all", steps="after"), - ]) + process.assert_called_once_with("foo", steps="all") store_report.assert_called_once() log_error.assert_not_called() process.reset_mock() log_error.reset_mock() store_report.reset_mock() - with self.subTest(action="print_balances"),\ - mock.patch.object(m, "print_balances") as print_balances: - m.process(["print_balances"]) + with self.subTest(before=True, after=True): + m.process(["sell_all"], before=True, after=True) - process.assert_not_called() - log_error.assert_not_called() + process.assert_called_once_with("sell_all", steps="all") store_report.assert_called_once() - print_balances.assert_called_once_with() - - log_error.reset_mock() - store_report.reset_mock() - with self.subTest(action="print_orders"),\ - mock.patch.object(m, "print_orders") as print_orders,\ - mock.patch.object(m, "print_balances") as print_balances: - m.process(["print_orders", "print_balances"]) - - process.assert_not_called() log_error.assert_not_called() - store_report.assert_called_once() - print_orders.assert_called_once_with() - print_balances.assert_called_once_with() - - log_error.reset_mock() - store_report.reset_mock() - with self.subTest(action="unknown"): - m.process(["unknown"]) - log_error.assert_called_once_with("market_process", message="Unknown action unknown") - store_report.assert_called_once() + process.reset_mock() log_error.reset_mock() store_report.reset_mock() with self.subTest(unhandled_exception=True): process.side_effect = Exception("bouh") - m.process(None, before=True) + m.process(["some_action"], before=True) log_error.assert_called_with("market_process", exception=mock.ANY) store_report.assert_called_once() @@ -768,24 +914,39 @@ class ProcessorTest(WebMockTestCase): with self.assertRaises(TypeError): processor.select_steps(scenario, ["wait"]) + def test_can_process(self): + processor = market.Processor(self.m) + + with self.subTest(True): + self.assertTrue(processor.can_process("sell_all")) + + with self.subTest(False): + self.assertFalse(processor.can_process("unknown_action")) + @mock.patch("market.Processor.process_step") def test_process(self, process_step): - processor = market.Processor(self.m) + with self.subTest("unknown action"): + processor = market.Processor(self.m) + with self.assertRaises(TypeError): + processor.process("unknown_action") + + with self.subTest("nominal case"): + processor = market.Processor(self.m) - processor.process("sell_all", foo="bar") - self.assertEqual(3, process_step.call_count) + processor.process("sell_all", foo="bar") + self.assertEqual(3, process_step.call_count) - steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls)) - scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls)) - kwargs = list(map(lambda x: x[1][2], process_step.mock_calls)) - self.assertEqual(["all_sell", "wait", "all_buy"], steps) - self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names) - self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs) + steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls)) + scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls)) + kwargs = list(map(lambda x: x[1][2], process_step.mock_calls)) + self.assertEqual(["all_sell", "wait", "all_buy"], steps) + self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names) + self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs) - process_step.reset_mock() + process_step.reset_mock() - processor.process("sell_needed", steps=["before", "after"]) - self.assertEqual(3, process_step.call_count) + processor.process("sell_needed", steps=["before", "after"]) + self.assertEqual(3, process_step.call_count) def test_method_arguments(self): ccxt = mock.Mock(spec=market.ccxt.poloniexE) @@ -799,7 +960,7 @@ class ProcessorTest(WebMockTestCase): method, arguments = processor.method_arguments("prepare_trades") self.assertEqual(m.prepare_trades, method) - self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments) + self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only', 'available_balance_only'], arguments) method, arguments = processor.method_arguments("prepare_orders") self.assertEqual(m.trades.prepare_orders, method) @@ -816,6 +977,9 @@ class ProcessorTest(WebMockTestCase): method, arguments = processor.method_arguments("close_trades") self.assertEqual(m.trades.close_trades, method) + method, arguments = processor.method_arguments("print_tickers") + self.assertEqual(m.print_tickers, method) + def test_process_step(self): processor = market.Processor(self.m) @@ -829,8 +993,8 @@ class ProcessorTest(WebMockTestCase): mock.call("process_foo__1_sell_end"), ]) self.m.balances.fetch_balances.assert_has_calls([ - mock.call(tag="process_foo__1_sell_begin"), - mock.call(tag="process_foo__1_sell_end"), + mock.call(tag="process_foo__1_sell_begin", log_tickers=True), + mock.call(tag="process_foo__1_sell_end", log_tickers=True), ]) self.assertEqual(5, run_action.call_count)