From 2033e7fef780298be2ec15455a0ec1d26515de55 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Tue, 27 Feb 2018 00:58:52 +0100 Subject: [PATCH] Add make_order and get_user_market helpers Fix cancel order not actually fetching the order Fetch only necessary order to poloniex --- helper.py | 56 +++++++++++++++++++++ portfolio.py | 9 ++-- test.py | 140 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 197 insertions(+), 8 deletions(-) diff --git a/helper.py b/helper.py index 4d73078..d948dac 100644 --- a/helper.py +++ b/helper.py @@ -7,6 +7,62 @@ import sys import portfolio +def make_order(market, value, currency, action="acquire", + close_if_possible=False, base_currency="BTC", follow=True, + compute_value="average"): + """ + Make an order on market + "market": The market on which to place the order + "value": The value in *base_currency* to acquire, + or in *currency* to dispose. + use negative for margin trade. + "action": "acquire" or "dispose". + "acquire" will buy long or sell short, + "dispose" will sell long or buy short. + "currency": The currency to acquire or dispose + "base_currency": The base currency. The value is expressed in that + currency (default: BTC) + "follow": Whether to follow the order once run (default: True) + "close_if_possible": Whether to try to close the position at the end + of the trade, i.e. reach exactly 0 at the end + (only meaningful in "dispose"). May have + unwanted effects if the end value of the + currency is not 0. + "compute_value": Compute value to place the order + """ + market.report.log_stage("make_order_begin") + market.balances.fetch_balances(tag="make_order_begin") + if action == "acquire": + trade = portfolio.Trade( + portfolio.Amount(base_currency, 0), + portfolio.Amount(base_currency, value), + currency, market) + else: + amount = portfolio.Amount(currency, value) + trade = portfolio.Trade( + amount.in_currency(base_currency, market, compute_value=compute_value), + portfolio.Amount(base_currency, 0), + currency, market) + market.trades.all.append(trade) + order = trade.prepare_order( + close_if_possible=close_if_possible, + compute_value=compute_value) + market.report.log_orders([order], None, compute_value) + market.trades.run_orders() + if follow: + market.follow_orders() + market.balances.fetch_balances(tag="make_order_end") + else: + market.report.log_stage("make_order_end_not_followed") + return order + market.report.log_stage("make_order_end") + +def get_user_market(config_path, user_id, debug=False): + import market + pg_config, report_path = main_parse_config(config_path) + market_config = list(main_fetch_markets(pg_config, str(user_id)))[0][0] + return market.Market.from_config(market_config, debug=debug) + def main_parse_args(argv): parser = argparse.ArgumentParser( description="Run the trade bot") diff --git a/portfolio.py b/portfolio.py index 6763fc6..eb3390e 100644 --- a/portfolio.py +++ b/portfolio.py @@ -360,7 +360,7 @@ class Trade: new_order.run() self.market.report.log_order(order, tick, new_order=new_order) - def prepare_order(self, compute_value="default"): + def prepare_order(self, close_if_possible=None, compute_value="default"): if self.action is None: return None ticker = self.market.get_ticker(self.currency, self.base_currency) @@ -426,7 +426,8 @@ class Trade: delta = delta - filled # I already sold 4 BTC, only 5 left - close_if_possible = (self.value_to == 0) + if close_if_possible is None: + close_if_possible = (self.value_to == 0) if delta <= 0: self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) @@ -586,7 +587,7 @@ class Order: return self.fetch_cache_timestamp = time.time() - result = self.market.ccxt.fetch_order(self.id) + result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency) self.results.append(result) self.status = result["status"] @@ -632,7 +633,7 @@ class Order: self.status = "canceled" return self.market.ccxt.cancel_order(self.id) - self.fetch() + self.fetch(force=True) class Mouvement: def __init__(self, currency, base_currency, hash_): diff --git a/test.py b/test.py index 955e2a1..778fc14 100644 --- a/test.py +++ b/test.py @@ -1458,6 +1458,25 @@ class TradeTest(WebMockTestCase): D("0.125"), "BTC", "long", self.m, trade, close_if_possible=False) + with self.subTest(action="dispose", inverted=False, close_if_possible=True): + filled_amount.return_value = portfolio.Amount("FOO", "60") + compute_value.return_value = D("0.125") + + value_from = portfolio.Amount("BTC", "10") + value_from.rate = D("0.1") + value_from.linked_to = portfolio.Amount("FOO", "100") + value_to = portfolio.Amount("BTC", "1") + trade = portfolio.Trade(value_from, value_to, "FOO", self.m) + + trade.prepare_order(close_if_possible=True) + + filled_amount.assert_called_with(in_base_currency=False) + compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default") + self.assertEqual(1, len(trade.orders)) + Order.assert_called_with("sell", portfolio.Amount("FOO", 30), + D("0.125"), "BTC", "long", self.m, + trade, close_if_possible=True) + with self.subTest(action="acquire", inverted=False): filled_amount.return_value = portfolio.Amount("BTC", "3") compute_value.return_value = D("0.125") @@ -1812,7 +1831,7 @@ class OrderTest(WebMockTestCase): order.cancel() self.m.ccxt.cancel_order.assert_called_with(42) - fetch.assert_called_once() + fetch.assert_called_once_with(force=True) self.m.report.log_debug_action.assert_not_called() def test_dust_amount_remaining(self): @@ -1966,7 +1985,7 @@ class OrderTest(WebMockTestCase): } order.fetch() - self.m.ccxt.fetch_order.assert_called_once() + self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") fetch_mouvements.assert_called_once() self.assertEqual("foo", order.status) self.assertEqual("timestamp", order.timestamp) @@ -1982,7 +2001,7 @@ class OrderTest(WebMockTestCase): fetch_mouvements.assert_not_called() order.fetch(force=True) - self.m.ccxt.fetch_order.assert_called_once() + self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") fetch_mouvements.assert_called_once() self.m.ccxt.fetch_order.reset_mock() @@ -1990,7 +2009,7 @@ class OrderTest(WebMockTestCase): time_mock.return_value = time + 19 order.fetch() - self.m.ccxt.fetch_order.assert_called_once() + self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") fetch_mouvements.assert_called_once() self.m.report.log_debug_action.assert_not_called() @@ -2586,6 +2605,119 @@ class ReportStoreTest(WebMockTestCase): @unittest.skipUnless("unit" in limits, "Unit skipped") class HelperTest(WebMockTestCase): + def test_make_order(self): + self.m.get_ticker.return_value = { + "inverted": False, + "average": D("0.1"), + "bid": D("0.09"), + "ask": D("0.11"), + } + + with self.subTest(description="nominal case"): + helper.make_order(self.m, 10, "ETH") + + self.m.report.log_stage.assert_has_calls([ + mock.call("make_order_begin"), + mock.call("make_order_end"), + ]) + self.m.balances.fetch_balances.assert_has_calls([ + mock.call(tag="make_order_begin"), + mock.call(tag="make_order_end"), + ]) + self.m.trades.all.append.assert_called_once() + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual(False, trade.orders[0].close_if_possible) + self.assertEqual(0, trade.value_from) + self.assertEqual("ETH", trade.currency) + self.assertEqual("BTC", trade.base_currency) + self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") + self.m.trades.run_orders.assert_called_once_with() + self.m.follow_orders.assert_called_once_with() + + order = trade.orders[0] + self.assertEqual(D("0.10"), order.rate) + + self.m.reset_mock() + with self.subTest(compute_value="default"): + helper.make_order(self.m, 10, "ETH", action="dispose", + compute_value="ask") + + trade = self.m.trades.all.append.mock_calls[0][1][0] + order = trade.orders[0] + self.assertEqual(D("0.11"), order.rate) + + self.m.reset_mock() + with self.subTest(follow=False): + result = helper.make_order(self.m, 10, "ETH", follow=False) + + self.m.report.log_stage.assert_has_calls([ + mock.call("make_order_begin"), + mock.call("make_order_end_not_followed"), + ]) + self.m.balances.fetch_balances.assert_called_once_with(tag="make_order_begin") + + self.m.trades.all.append.assert_called_once() + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual(0, trade.value_from) + self.assertEqual("ETH", trade.currency) + self.assertEqual("BTC", trade.base_currency) + self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") + self.m.trades.run_orders.assert_called_once_with() + self.m.follow_orders.assert_not_called() + self.assertEqual(trade.orders[0], result) + + self.m.reset_mock() + with self.subTest(base_currency="USDT"): + helper.make_order(self.m, 1, "BTC", base_currency="USDT") + + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual("BTC", trade.currency) + self.assertEqual("USDT", trade.base_currency) + + self.m.reset_mock() + with self.subTest(close_if_possible=True): + helper.make_order(self.m, 10, "ETH", close_if_possible=True) + + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual(True, trade.orders[0].close_if_possible) + + self.m.reset_mock() + with self.subTest(action="dispose"): + helper.make_order(self.m, 10, "ETH", action="dispose") + + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual(0, trade.value_to) + self.assertEqual(1, trade.value_from.value) + self.assertEqual("ETH", trade.currency) + self.assertEqual("BTC", trade.base_currency) + + self.m.reset_mock() + with self.subTest(compute_value="default"): + helper.make_order(self.m, 10, "ETH", action="dispose", + compute_value="bid") + + trade = self.m.trades.all.append.mock_calls[0][1][0] + self.assertEqual(D("0.9"), trade.value_from.value) + + def test_user_market(self): + with mock.patch("helper.main_fetch_markets") as main_fetch_markets,\ + mock.patch("helper.main_parse_config") as main_parse_config: + with self.subTest(debug=False): + main_parse_config.return_value = ["pg_config", "report_path"] + main_fetch_markets.return_value = [({"key": "market_config"},)] + m = helper.get_user_market("config_path.ini", 1) + + self.assertIsInstance(m, market.Market) + self.assertFalse(m.debug) + + with self.subTest(debug=True): + main_parse_config.return_value = ["pg_config", "report_path"] + main_fetch_markets.return_value = [({"key": "market_config"},)] + m = helper.get_user_market("config_path.ini", 1, debug=True) + + self.assertIsInstance(m, market.Market) + self.assertTrue(m.debug) + def test_main_store_report(self): file_open = mock.mock_open() with self.subTest(file=None), mock.patch("__main__.open", file_open): -- 2.41.0