From 5a72ded790f8b5e7c9b38a3cc91c12fbfb6cb97a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sun, 11 Feb 2018 22:40:30 +0100 Subject: [PATCH] Add missing tests --- helper.py | 39 ++-- portfolio.py | 55 +++--- test.py | 511 ++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 520 insertions(+), 85 deletions(-) diff --git a/helper.py b/helper.py index 8a29f40..421e8cd 100644 --- a/helper.py +++ b/helper.py @@ -115,16 +115,33 @@ def print_orders(market, base_currency="BTC"): print(balance) TradeStore.print_all_with_order() -def make_orders(market, base_currency="BTC"): - prepare_trades(market, base_currency=base_currency) - for trade in TradeStore.all: - print(trade) - for order in trade.orders: - print("\t", order, sep="") - order.run() +def process_sell_needed__1_sell(market, base_currency="BTC", debug=False): + prepare_trades(market, base_currency=base_currency, debug=debug) + TradeStore.prepare_orders(compute_value="average", only="dispose") + print("------------------") + for currency, balance in BalanceStore.all.items(): + print(balance) + print("------------------") + TradeStore.print_all_with_order() + print("------------------") + TradeStore.run_orders() + follow_orders() + +def process_sell_needed__2_sell(market, base_currency="BTC", debug=False): + update_trades(market, base_currency=base_currency, debug=debug, only="acquire") + TradeStore.prepare_orders(compute_value="average", only="acquire") + print("------------------") + for currency, balance in BalanceStore.all.items(): + print(balance) + print("------------------") + TradeStore.print_all_with_order() + print("------------------") + move_balances(market, debug=debug) + TradeStore.run_orders() + follow_orders() -def process_sell_all_sell(market, base_currency="BTC", debug=False): - prepare_trades_to_sell_all(market, debug=debug) +def process_sell_all__1_all_sell(market, base_currency="BTC", debug=False): + prepare_trades_to_sell_all(market, base_currency=base_currency, debug=debug) TradeStore.prepare_orders(compute_value="average") print("------------------") for currency, balance in BalanceStore.all.items(): @@ -135,8 +152,8 @@ def process_sell_all_sell(market, base_currency="BTC", debug=False): TradeStore.run_orders() follow_orders() -def process_sell_all_buy(market, base_currency="BTC", debug=False): - prepare_trades(market, debug=debug) +def process_sell_all__2_all_buy(market, base_currency="BTC", debug=False): + prepare_trades(market, base_currency=base_currency, debug=debug) TradeStore.prepare_orders() print("------------------") for currency, balance in BalanceStore.all.items(): diff --git a/portfolio.py b/portfolio.py index b629966..e98689e 100644 --- a/portfolio.py +++ b/portfolio.py @@ -1,4 +1,5 @@ import time +from datetime import datetime from decimal import Decimal as D, ROUND_DOWN # Put your poloniex api key in market.py from json import JSONDecodeError @@ -272,7 +273,7 @@ class Trade: if self.base_currency == self.currency: return None - if self.value_from < self.value_to: + if abs(self.value_from) < abs(self.value_to): return "acquire" else: return "dispose" @@ -301,19 +302,16 @@ class Trade: if tick in [0, 1, 3, 4, 6]: print("{}, tick {}, waiting".format(order, tick)) elif tick == 2: - self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) - new_order = self.orders[-1] + new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) elif tick ==5: - self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) - new_order = self.orders[-1] + new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) elif tick >= 7: if tick == 7: print("{}, tick {}, fallbacking to market value".format(order, tick)) if (tick - 7) % 3 == 0: - self.prepare_order(compute_value="default") - new_order = self.orders[-1] + new_order = self.prepare_order(compute_value="default") print("{}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) if new_order is not None: @@ -322,7 +320,7 @@ class Trade: def prepare_order(self, compute_value="default"): if self.action is None: - return + return None ticker = h.get_ticker(self.currency, self.base_currency, self.market) inverted = ticker["inverted"] if inverted: @@ -387,11 +385,13 @@ class Trade: if delta <= 0: print("Less to do than already filled: {}".format(delta)) - return + return None - self.orders.append(Order(self.order_action(inverted), + order = Order(self.order_action(inverted), delta, rate, base_currency, self.trade_type, - self.market, self, close_if_possible=close_if_possible)) + self.market, self, close_if_possible=close_if_possible) + self.orders.append(order) + return order def __repr__(self): return "Trade({} -> {} in {}, {})".format( @@ -419,6 +419,8 @@ class Order: self.status = "pending" self.trade = trade self.close_if_possible = close_if_possible + self.id = None + self.fetch_cache_timestamp = None def __repr__(self): return "Order({} {} {} at {} {} [{}]{})".format( @@ -438,6 +440,10 @@ class Order: else: return "margin" + @property + def open(self): + return self.status == "open" + @property def pending(self): return self.status == "pending" @@ -446,10 +452,6 @@ class Order: def finished(self): return self.status == "closed" or self.status == "canceled" or self.status == "error" - @property - def id(self): - return self.results[0]["id"] - def run(self): symbol = "{}/{}".format(self.amount.currency, self.base_currency) amount = round(self.amount, self.market.order_precision(symbol)).value @@ -457,26 +459,27 @@ class Order: if TradeStore.debug: print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( symbol, self.action, amount, self.rate, self.account)) - self.status = "open" self.results.append({"debug": True, "id": -1}) else: try: self.results.append(self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)) - self.status = "open" except Exception as e: self.status = "error" print("error when running market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( symbol, self.action, amount, self.rate, self.account)) self.error_message = str("{}: {}".format(e.__class__.__name__, e)) print(self.error_message) + return + self.id = self.results[0]["id"] + self.status = "open" def get_status(self): if TradeStore.debug: return self.status # other states are "closed" and "canceled" - if self.status == "open": + if not self.finished: self.fetch() - if self.status != "open": + if self.finished: self.mark_finished_order() return self.status @@ -487,15 +490,15 @@ class Order: if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: self.market.close_margin_position(self.amount.currency, self.base_currency) - fetch_cache_timestamp = None def fetch(self, force=False): if TradeStore.debug or (not force and self.fetch_cache_timestamp is not None and time.time() - self.fetch_cache_timestamp < 10): return self.fetch_cache_timestamp = time.time() - self.results.append(self.market.fetch_order(self.id)) - result = self.results[-1] + result = self.market.fetch_order(self.id) + self.results.append(result) + self.status = result["status"] # Time at which the order started self.timestamp = result["datetime"] @@ -503,11 +506,9 @@ class Order: # FIXME: consider open order with dust remaining as closed - @property def dust_amount_remaining(self): - return self.remaining_amount < 0.001 + return self.remaining_amount() < Amount(self.amount.currency, D("0.001")) - @property def remaining_amount(self): if self.status == "open": self.fetch() @@ -536,7 +537,7 @@ class Order: if TradeStore.debug: self.status = "canceled" return - self.market.cancel_order(self.result['id']) + self.market.cancel_order(self.id) self.fetch() class Mouvement: @@ -552,6 +553,6 @@ class Mouvement: # rate * total = total_in_base self.total_in_base = Amount(base_currency, hash_["total"]) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover from market import market h.print_orders(market) diff --git a/test.py b/test.py index be3ad4a..8903815 100644 --- a/test.py +++ b/test.py @@ -43,7 +43,6 @@ class WebMockTestCase(unittest.TestCase): for patcher in self.patchers: patcher.start() - def tearDown(self): for patcher in self.patchers: patcher.stop() @@ -702,6 +701,68 @@ class HelperTest(WebMockTestCase): else: self.assertEqual("", stdout_mock.getvalue()) + @mock.patch.object(portfolio.BalanceStore, "fetch_balances") + def test_move_balance(self, fetch_balances): + for debug in [True, False]: + with self.subTest(debug=debug),\ + mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: + value_from = portfolio.Amount("BTC", "1.0") + value_from.linked_to = portfolio.Amount("ETH", "10.0") + value_to = portfolio.Amount("BTC", "10.0") + trade1 = portfolio.Trade(value_from, value_to, "ETH") + + value_from = portfolio.Amount("BTC", "0.0") + value_from.linked_to = portfolio.Amount("ETH", "0.0") + value_to = portfolio.Amount("BTC", "-3.0") + trade2 = portfolio.Trade(value_from, value_to, "ETH") + + value_from = portfolio.Amount("USDT", "0.0") + value_from.linked_to = portfolio.Amount("XVG", "0.0") + value_to = portfolio.Amount("USDT", "-50.0") + trade3 = portfolio.Trade(value_from, value_to, "XVG") + + portfolio.TradeStore.all = [trade1, trade2, trade3] + balance1 = portfolio.Balance("BTC", { "margin_free": "0" }) + balance2 = portfolio.Balance("USDT", { "margin_free": "100" }) + portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2} + + market = mock.Mock() + + helper.move_balances(market, debug=debug) + + fetch_balances.assert_called_with(market) + if debug: + self.assertRegex(stdout_mock.getvalue(), "market.transfer_balance") + else: + market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin") + market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange") + + @mock.patch.object(helper, "prepare_trades") + @mock.patch.object(portfolio.TradeStore, "prepare_orders") + @mock.patch.object(portfolio.TradeStore, "print_all_with_order") + @mock.patch('sys.stdout', new_callable=StringIO) + def test_print_orders(self, stdout_mock, print_all_with_order, prepare_orders, prepare_trades): + market = mock.Mock() + portfolio.BalanceStore.all = { + "BTC": portfolio.Balance("BTC", { + "total": "0.65", + "exchange_total":"0.65", + "exchange_free": "0.35", + "exchange_used": "0.30"}), + "ETH": portfolio.Balance("ETH", { + "total": 3, + "exchange_total": 3, + "exchange_free": 3, + "exchange_used": 0}), + } + helper.print_orders(market) + prepare_trades.assert_called_with(market, base_currency="BTC", + compute_value="average") + prepare_orders.assert_called_with(compute_value="average") + print_all_with_order.assert_called() + self.assertRegex(stdout_mock.getvalue(), "Balance") + + @unittest.skipUnless("unit" in limits, "Unit skipped") class TradeStoreTest(WebMockTestCase): @mock.patch.object(portfolio.BalanceStore, "currencies") @@ -1068,7 +1129,7 @@ class TradeTest(WebMockTestCase): value_to = portfolio.Amount("BTC", "-1.0") trade = portfolio.Trade(value_from, value_to, "ETH") - self.assertEqual("dispose", trade.action) + self.assertEqual("acquire", trade.action) def test_order_action(self): value_from = portfolio.Amount("BTC", "0.5") @@ -1275,9 +1336,7 @@ class TradeTest(WebMockTestCase): value_from.linked_to = portfolio.Amount("ETH", "10.0") value_to = portfolio.Amount("BTC", "1.0") trade = portfolio.Trade(value_from, value_to, "ETH") - def _prepare_order(compute_value=None): - trade.orders.append(new_order_mock) - prepare_order.side_effect = _prepare_order + prepare_order.return_value = new_order_mock for i in [0, 1, 3, 4, 6]: with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: @@ -1285,7 +1344,6 @@ class TradeTest(WebMockTestCase): order_mock.cancel.assert_not_called() new_order_mock.run.assert_not_called() self.assertRegex(stdout_mock.getvalue(), "tick {}, waiting".format(i)) - self.assertEqual(0, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1297,7 +1355,6 @@ class TradeTest(WebMockTestCase): new_order_mock.run.assert_called() prepare_order.assert_called() self.assertRegex(stdout_mock.getvalue(), "tick 2, cancelling and adjusting") - self.assertEqual(1, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1309,7 +1366,6 @@ class TradeTest(WebMockTestCase): new_order_mock.run.assert_called() prepare_order.assert_called() self.assertRegex(stdout_mock.getvalue(), "tick 5, cancelling and adjusting") - self.assertEqual(1, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1322,7 +1378,6 @@ class TradeTest(WebMockTestCase): prepare_order.assert_called_with(compute_value="default") self.assertRegex(stdout_mock.getvalue(), "tick 7, fallbacking to market value") self.assertRegex(stdout_mock.getvalue(), "tick 7, market value, cancelling and adjusting to") - self.assertEqual(1, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1336,7 +1391,6 @@ class TradeTest(WebMockTestCase): prepare_order.assert_called_with(compute_value="default") self.assertNotRegex(stdout_mock.getvalue(), "tick {}, fallbacking to market value".format(i)) self.assertRegex(stdout_mock.getvalue(), "tick {}, market value, cancelling and adjusting to".format(i)) - self.assertEqual(1, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1348,7 +1402,6 @@ class TradeTest(WebMockTestCase): order_mock.cancel.assert_not_called() new_order_mock.run.assert_not_called() self.assertEqual("", stdout_mock.getvalue()) - self.assertEqual(0, len(trade.orders)) order_mock.reset_mock() new_order_mock.reset_mock() @@ -1386,6 +1439,355 @@ class TradeTest(WebMockTestCase): self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade)) +@unittest.skipUnless("unit" in limits, "Unit skipped") +class OrderTest(WebMockTestCase): + def test_values(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertEqual("buy", order.action) + self.assertEqual(10, order.amount.value) + self.assertEqual("ETH", order.amount.currency) + self.assertEqual(D("0.1"), order.rate) + self.assertEqual("BTC", order.base_currency) + self.assertEqual("market", order.market) + self.assertEqual("long", order.trade_type) + self.assertEqual("pending", order.status) + self.assertEqual("trade", order.trade) + self.assertIsNone(order.id) + self.assertFalse(order.close_if_possible) + + def test__repr(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order)) + + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade", + close_if_possible=True) + self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order)) + + def test_account(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertEqual("exchange", order.account) + + order = portfolio.Order("sell", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "short", "market", "trade") + self.assertEqual("margin", order.account) + + def test_pending(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertTrue(order.pending) + order.status = "open" + self.assertFalse(order.pending) + + def test_open(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertFalse(order.open) + order.status = "open" + self.assertTrue(order.open) + + def test_finished(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertFalse(order.finished) + order.status = "closed" + self.assertTrue(order.finished) + order.status = "canceled" + self.assertTrue(order.finished) + order.status = "error" + self.assertTrue(order.finished) + + @mock.patch.object(portfolio.Order, "fetch") + def test_cancel(self, fetch): + market = mock.Mock() + portfolio.TradeStore.debug = True + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + order.status = "open" + + order.cancel() + market.cancel_order.assert_not_called() + self.assertEqual("canceled", order.status) + + portfolio.TradeStore.debug = False + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + order.status = "open" + order.id = 42 + + order.cancel() + market.cancel_order.assert_called_with(42) + fetch.assert_called_once() + + def test_dust_amount_remaining(self): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1)) + self.assertFalse(order.dust_amount_remaining()) + + order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001"))) + self.assertTrue(order.dust_amount_remaining()) + + @mock.patch.object(portfolio.Order, "fetch") + @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1)) + def test_remaining_amount(self, filled_amount, fetch): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + + self.assertEqual(9, order.remaining_amount().value) + order.fetch.assert_not_called() + + order.status = "open" + self.assertEqual(9, order.remaining_amount().value) + fetch.assert_called_once() + + @mock.patch.object(portfolio.Order, "fetch") + def test_filled_amount(self, fetch): + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + order.mouvements.append(portfolio.Mouvement("ETH", "BTC", { + "id": 42, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 12:00:12", "rate": "0.1", + "amount": "3", "total": "0.3" + })) + order.mouvements.append(portfolio.Mouvement("ETH", "BTC", { + "id": 43, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 13:00:12", "rate": "0.2", + "amount": "2", "total": "0.4" + })) + self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount()) + fetch.assert_not_called() + order.status = "open" + self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False)) + fetch.assert_called_once() + self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True)) + + def test_fetch_mouvements(self): + market = mock.Mock() + market.privatePostReturnOrderTrades.return_value = [ + { + "id": 42, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 12:00:12", "rate": "0.1", + "amount": "3", "total": "0.3" + }, + { + "id": 43, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 13:00:12", "rate": "0.2", + "amount": "2", "total": "0.4" + } + ] + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + order.id = 12 + order.mouvements = ["Foo", "Bar", "Baz"] + + order.fetch_mouvements() + + market.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12}) + self.assertEqual(2, len(order.mouvements)) + self.assertEqual(42, order.mouvements[0].id) + self.assertEqual(43, order.mouvements[1].id) + + def test_mark_finished_order(self): + market = mock.Mock() + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "short", market, "trade", + close_if_possible=True) + order.status = "closed" + portfolio.TradeStore.debug = False + + order.mark_finished_order() + market.close_margin_position.assert_called_with("ETH", "BTC") + market.close_margin_position.reset_mock() + + order.status = "open" + order.mark_finished_order() + market.close_margin_position.assert_not_called() + + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "short", market, "trade", + close_if_possible=False) + order.status = "closed" + order.mark_finished_order() + market.close_margin_position.assert_not_called() + + order = portfolio.Order("sell", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "short", market, "trade", + close_if_possible=True) + order.status = "closed" + order.mark_finished_order() + market.close_margin_position.assert_not_called() + + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade", + close_if_possible=True) + order.status = "closed" + order.mark_finished_order() + market.close_margin_position.assert_not_called() + + portfolio.TradeStore.debug = True + + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "short", market, "trade", + close_if_possible=True) + order.status = "closed" + + order.mark_finished_order() + market.close_margin_position.assert_not_called() + + @mock.patch.object(portfolio.Order, "fetch_mouvements") + def test_fetch(self, fetch_mouvements): + time = self.time.time() + with mock.patch.object(portfolio.time, "time") as time_mock: + market = mock.Mock() + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + order.id = 45 + with self.subTest(debug=True): + portfolio.TradeStore.debug = True + order.fetch() + time_mock.assert_not_called() + order.fetch(force=True) + time_mock.assert_not_called() + market.fetch_order.assert_not_called() + fetch_mouvements.assert_not_called() + self.assertIsNone(order.fetch_cache_timestamp) + + with self.subTest(debug=False): + portfolio.TradeStore.debug = False + time_mock.return_value = time + market.fetch_order.return_value = { + "status": "foo", + "datetime": "timestamp" + } + order.fetch() + + market.fetch_order.assert_called_once() + fetch_mouvements.assert_called_once() + self.assertEqual("foo", order.status) + self.assertEqual("timestamp", order.timestamp) + self.assertEqual(time, order.fetch_cache_timestamp) + self.assertEqual(1, len(order.results)) + + market.fetch_order.reset_mock() + fetch_mouvements.reset_mock() + + time_mock.return_value = time + 8 + order.fetch() + market.fetch_order.assert_not_called() + fetch_mouvements.assert_not_called() + + order.fetch(force=True) + market.fetch_order.assert_called_once() + fetch_mouvements.assert_called_once() + + market.fetch_order.reset_mock() + fetch_mouvements.reset_mock() + + time_mock.return_value = time + 19 + order.fetch() + market.fetch_order.assert_called_once() + fetch_mouvements.assert_called_once() + + @mock.patch.object(portfolio.Order, "fetch") + @mock.patch.object(portfolio.Order, "mark_finished_order") + def test_get_status(self, mark_finished_order, fetch): + with self.subTest(debug=True): + portfolio.TradeStore.debug = True + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + self.assertEqual("pending", order.get_status()) + fetch.assert_not_called() + + with self.subTest(debug=False, finished=False): + portfolio.TradeStore.debug = False + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + def _fetch(order): + def update_status(): + order.status = "open" + return update_status + fetch.side_effect = _fetch(order) + self.assertEqual("open", order.get_status()) + mark_finished_order.assert_not_called() + fetch.assert_called_once() + + mark_finished_order.reset_mock() + fetch.reset_mock() + with self.subTest(debug=False, finished=True): + portfolio.TradeStore.debug = False + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", "market", "trade") + def _fetch(order): + def update_status(): + order.status = "closed" + return update_status + fetch.side_effect = _fetch(order) + self.assertEqual("closed", order.get_status()) + mark_finished_order.assert_called_once() + fetch.assert_called_once() + + def test_run(self): + market = mock.Mock() + + market.order_precision.return_value = 4 + with self.subTest(debug=True),\ + mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: + portfolio.TradeStore.debug = True + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + order.run() + market.create_order.assert_not_called() + self.assertEqual("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)\n", stdout_mock.getvalue()) + self.assertEqual("open", order.status) + self.assertEqual(1, len(order.results)) + self.assertEqual(-1, order.id) + + market.create_order.reset_mock() + with self.subTest(debug=False): + portfolio.TradeStore.debug = False + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + market.create_order.return_value = { "id": 123 } + order.run() + market.create_order.assert_called_once() + self.assertEqual(1, len(order.results)) + self.assertEqual("open", order.status) + + market.create_order.reset_mock() + with self.subTest(exception=True),\ + mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: + order = portfolio.Order("buy", portfolio.Amount("ETH", 10), + D("0.1"), "BTC", "long", market, "trade") + market.create_order.side_effect = Exception("bouh") + order.run() + market.create_order.assert_called_once() + self.assertEqual(0, len(order.results)) + self.assertEqual("error", order.status) + self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order") + self.assertRegex(stdout_mock.getvalue(), "Exception: bouh") + +@unittest.skipUnless("unit" in limits, "Unit skipped") +class MouvementTest(WebMockTestCase): + def test_values(self): + mouvement = portfolio.Mouvement("ETH", "BTC", { + "id": 42, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 12:00:12", "rate": "0.1", + "amount": "10", "total": "1" + }) + self.assertEqual("ETH", mouvement.currency) + self.assertEqual("BTC", mouvement.base_currency) + self.assertEqual(42, mouvement.id) + self.assertEqual("buy", mouvement.action) + self.assertEqual(D("0.0015"), mouvement.fee_rate) + self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), mouvement.date) + self.assertEqual(D("0.1"), mouvement.rate) + self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total) + self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base) + @unittest.skipUnless("acceptance" in limits, "Acceptance skipped") class AcceptanceTest(WebMockTestCase): @unittest.expectedFailure @@ -1473,7 +1875,7 @@ class AcceptanceTest(WebMockTestCase): self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total) - trades = TradeStore.all + trades = portfolio.TradeStore.all self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from) self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to) self.assertEqual("dispose", trades[0].action) @@ -1488,7 +1890,7 @@ class AcceptanceTest(WebMockTestCase): self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from) self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to) - self.assertEqual("dispose", trades[3].action) + self.assertEqual("acquire", trades[3].action) self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from) self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to) @@ -1499,9 +1901,9 @@ class AcceptanceTest(WebMockTestCase): self.assertEqual("acquire", trades[5].action) # Action 2 - portfolio.Trade.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001")) + portfolio.TradeStore.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001")) - all_orders = portfolio.Trade.all_orders() + all_orders = portfolio.TradeStore.all_orders(state="pending") self.assertEqual(2, len(all_orders)) self.assertEqual(2, 3*all_orders[0].amount.value) self.assertEqual(D("0.14014"), all_orders[0].rate) @@ -1534,7 +1936,14 @@ class AcceptanceTest(WebMockTestCase): self.assertEqual("open", all_orders[0].status) self.assertEqual("open", all_orders[1].status) - market.fetch_order.return_value = { "status": "closed" } + market.fetch_order.return_value = { "status": "closed", "datetime": "2018-01-20 13:40:00" } + market.privatePostReturnOrderTrades.return_value = [ + { + "id": 42, "type": "buy", "fee": "0.0015", + "date": "2017-12-30 12:00:12", "rate": "0.1", + "amount": "10", "total": "1" + } + ] with mock.patch.object(portfolio.time, "sleep") as sleep: # Action 4 helper.follow_orders(verbose=False) @@ -1546,31 +1955,39 @@ class AcceptanceTest(WebMockTestCase): fetch_balance = { "ETH": { - "free": D("1.0") / 3, - "used": D("0.0"), + "exchange_free": D("1.0") / 3, + "exchange_used": D("0.0"), + "exchange_total": D("1.0") / 3, + "margin_total": 0, "total": D("1.0") / 3, }, "BTC": { - "free": D("0.134"), - "used": D("0.0"), + "exchange_free": D("0.134"), + "exchange_used": D("0.0"), + "exchange_total": D("0.134"), + "margin_total": 0, "total": D("0.134"), }, "ETC": { - "free": D("4.0"), - "used": D("0.0"), + "exchange_free": D("4.0"), + "exchange_used": D("0.0"), + "exchange_total": D("4.0"), + "margin_total": 0, "total": D("4.0"), }, "XVG": { - "free": D("0.0"), - "used": D("0.0"), + "exchange_free": D("0.0"), + "exchange_used": D("0.0"), + "exchange_total": D("0.0"), + "margin_total": 0, "total": D("0.0"), }, } - market.fetch_balance.return_value = fetch_balance + market.fetch_all_balances.return_value = fetch_balance with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): # Action 5 - helper.update_trades(market, only="buy", compute_value="average") + helper.update_trades(market, only="acquire", compute_value="average") balances = portfolio.BalanceStore.all self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total) @@ -1579,37 +1996,37 @@ class AcceptanceTest(WebMockTestCase): self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total) - trades = TradeStore.all - self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to) - self.assertEqual("sell", trades["ETH"].action) + trades = portfolio.TradeStore.all + self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to) + self.assertEqual("dispose", trades[0].action) - self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.0485")), trades["ETC"].value_to) - self.assertEqual("buy", trades["ETC"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to) + self.assertEqual("acquire", trades[1].action) self.assertNotIn("BTC", trades) - self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.00194")), trades["BTD"].value_to) - self.assertEqual("buy", trades["BTD"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to) + self.assertEqual("dispose", trades[2].action) - self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.00776")), trades["B2X"].value_to) - self.assertEqual("buy", trades["B2X"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from) + self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to) + self.assertEqual("acquire", trades[3].action) - self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["USDT"].value_to) - self.assertEqual("buy", trades["USDT"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to) + self.assertEqual("acquire", trades[4].action) - self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades["XVG"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to) - self.assertEqual("sell", trades["XVG"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to) + self.assertEqual("acquire", trades[5].action) # Action 6 - portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"]) + portfolio.TradeStore.prepare_orders(only="acquire", compute_value=lambda x, y: x["ask"]) - all_orders = portfolio.Trade.all_orders(state="pending") + all_orders = portfolio.TradeStore.all_orders(state="pending") self.assertEqual(4, len(all_orders)) self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount)) self.assertEqual(D("0.003"), all_orders[0].rate) -- 2.41.0