+ def test__repr(self):
+ self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
+ repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
+ balance = portfolio.Balance("BTX", { "exchange_total": 3,
+ "exchange_used": 1, "exchange_free": 2 })
+ self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
+
+ balance = portfolio.Balance("BTX", { "exchange_total": 1, "exchange_used": 1})
+ self.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance))
+
+ balance = portfolio.Balance("BTX", { "margin_total": 3,
+ "margin_in_position": 1, "margin_available": 2 })
+ self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
+
+ balance = portfolio.Balance("BTX", { "margin_total": 2, "margin_available": 2 })
+ self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance))
+
+ balance = portfolio.Balance("BTX", { "margin_total": -3,
+ "margin_borrowed_base_price": D("0.1"),
+ "margin_borrowed_base_currency": "BTC",
+ "margin_lending_fees": D("0.002") })
+ self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
+
+ balance = portfolio.Balance("BTX", { "margin_total": 1,
+ "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
+ self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))
+
+ def test_as_json(self):
+ balance = portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })
+ as_json = balance.as_json()
+ self.assertEqual(set(portfolio.Balance.base_keys), set(as_json.keys()))
+ self.assertEqual(D(0), as_json["total"])
+ self.assertEqual(D(2), as_json["exchange_total"])
+ self.assertEqual(D(2), as_json["exchange_free"])
+ self.assertEqual(D(0), as_json["exchange_used"])
+ self.assertEqual(D(0), as_json["margin_total"])
+ self.assertEqual(D(0), as_json["margin_available"])
+ self.assertEqual(D(0), as_json["margin_borrowed"])
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class MarketTest(WebMockTestCase):
+ def setUp(self):
+ super(MarketTest, self).setUp()
+
+ self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
+
+ def test_values(self):
+ m = market.Market(self.ccxt)
+
+ self.assertEqual(self.ccxt, m.ccxt)
+ self.assertFalse(m.debug)
+ self.assertIsInstance(m.report, market.ReportStore)
+ self.assertIsInstance(m.trades, market.TradeStore)
+ self.assertIsInstance(m.balances, market.BalanceStore)
+ self.assertEqual(m, m.report.market)
+ self.assertEqual(m, m.trades.market)
+ self.assertEqual(m, m.balances.market)
+ self.assertEqual(m, m.ccxt._market)
+
+ m = market.Market(self.ccxt, debug=True)
+ self.assertTrue(m.debug)
+
+ m = market.Market(self.ccxt, debug=False)
+ self.assertFalse(m.debug)
+
+ @mock.patch("market.ccxt")
+ def test_from_config(self, ccxt):
+ with mock.patch("market.ReportStore"):
+ ccxt.poloniexE.return_value = self.ccxt
+ self.ccxt.session.request.return_value = "response"
+
+ m = market.Market.from_config({"key": "key", "secred": "secret"})
+
+ self.assertEqual(self.ccxt, m.ccxt)
+
+ self.ccxt.session.request("GET", "URL", data="data",
+ headers="headers")
+ m.report.log_http_request.assert_called_with('GET', 'URL', 'data',
+ 'headers', 'response')
+
+ m = market.Market.from_config({"key": "key", "secred": "secret"}, debug=True)
+ self.assertEqual(True, m.debug)
+
+ def test_get_tickers(self):
+ self.ccxt.fetch_tickers.side_effect = [
+ "tickers",
+ market.NotSupported
+ ]
+
+ m = market.Market(self.ccxt)
+ self.assertEqual("tickers", m.get_tickers())
+ self.assertEqual("tickers", m.get_tickers())
+ self.ccxt.fetch_tickers.assert_called_once()
+
+ self.assertIsNone(m.get_tickers(refresh=self.time.time()))
+
+ def test_get_ticker(self):
+ with self.subTest(get_tickers=True):
+ self.ccxt.fetch_tickers.return_value = {
+ "ETH/ETC": { "bid": 1, "ask": 3 },
+ "XVG/ETH": { "bid": 10, "ask": 40 },
+ }
+ m = market.Market(self.ccxt)
+
+ ticker = m.get_ticker("ETH", "ETC")
+ self.assertEqual(1, ticker["bid"])
+ self.assertEqual(3, ticker["ask"])
+ self.assertEqual(2, ticker["average"])
+ self.assertFalse(ticker["inverted"])
+
+ ticker = m.get_ticker("ETH", "XVG")
+ self.assertEqual(0.0625, ticker["average"])
+ self.assertTrue(ticker["inverted"])
+ self.assertIn("original", ticker)
+ self.assertEqual(10, ticker["original"]["bid"])
+ self.assertEqual(25, ticker["original"]["average"])
+
+ ticker = m.get_ticker("XVG", "XMR")
+ self.assertIsNone(ticker)
+
+ with self.subTest(get_tickers=False):
+ self.ccxt.fetch_tickers.return_value = None
+ self.ccxt.fetch_ticker.side_effect = [
+ { "bid": 1, "ask": 3 },
+ market.ExchangeError("foo"),
+ { "bid": 10, "ask": 40 },
+ market.ExchangeError("foo"),
+ market.ExchangeError("foo"),
+ ]
+
+ m = market.Market(self.ccxt)
+
+ ticker = m.get_ticker("ETH", "ETC")
+ self.ccxt.fetch_ticker.assert_called_with("ETH/ETC")
+ self.assertEqual(1, ticker["bid"])
+ self.assertEqual(3, ticker["ask"])
+ self.assertEqual(2, ticker["average"])
+ self.assertFalse(ticker["inverted"])
+
+ ticker = m.get_ticker("ETH", "XVG")
+ self.assertEqual(0.0625, ticker["average"])
+ self.assertTrue(ticker["inverted"])
+ self.assertIn("original", ticker)
+ self.assertEqual(10, ticker["original"]["bid"])
+ self.assertEqual(25, ticker["original"]["average"])
+
+ ticker = m.get_ticker("XVG", "XMR")
+ self.assertIsNone(ticker)
+
+ def test_fetch_fees(self):
+ m = market.Market(self.ccxt)
+ self.ccxt.fetch_fees.return_value = "Foo"
+ self.assertEqual("Foo", m.fetch_fees())
+ self.ccxt.fetch_fees.assert_called_once()
+ self.ccxt.reset_mock()
+ self.assertEqual("Foo", m.fetch_fees())
+ self.ccxt.fetch_fees.assert_not_called()
+
+ @mock.patch.object(market.Portfolio, "repartition")
+ @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") }
+ if c1 == "XEM" and c2 == "BTC":
+ return { "average": D("0.001") }
+ self.fail("Should be called with {}, {}".format(c1, c2))
+ get_ticker.side_effect = _get_ticker
+
+ with mock.patch("market.ReportStore"):
+ m = market.Market(self.ccxt)
+ self.ccxt.fetch_all_balances.return_value = {
+ "USDT": {
+ "exchange_free": D("10000.0"),
+ "exchange_used": D("0.0"),
+ "exchange_total": D("10000.0"),
+ "total": D("10000.0")
+ },
+ "XVG": {
+ "exchange_free": D("10000.0"),
+ "exchange_used": D("0.0"),
+ "exchange_total": D("10000.0"),
+ "total": D("10000.0")
+ },
+ }
+
+ m.balances.fetch_balances(tag="tag")
+
+ m.prepare_trades()
+ compute_trades.assert_called()
+
+ call = compute_trades.call_args
+ self.assertEqual(1, call[0][0]["USDT"].value)
+ self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
+ self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
+ 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)
+ m.report.log_balances.assert_called_once_with(tag="tag")
+
+
+ @mock.patch.object(market.time, "sleep")
+ @mock.patch.object(market.TradeStore, "all_orders")
+ def test_follow_orders(self, all_orders, time_mock):
+ for debug, sleep in [
+ (False, None), (True, None),
+ (False, 12), (True, 12)]:
+ with self.subTest(sleep=sleep, debug=debug), \
+ mock.patch("market.ReportStore"):
+ m = market.Market(self.ccxt, debug=debug)
+
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ order_mock3 = mock.Mock()
+ all_orders.side_effect = [
+ [order_mock1, order_mock2],
+ [order_mock1, order_mock2],
+
+ [order_mock1, order_mock3],
+ [order_mock1, order_mock3],
+
+ [order_mock1, order_mock3],
+ [order_mock1, order_mock3],
+
+ []
+ ]
+
+ order_mock1.get_status.side_effect = ["open", "open", "closed"]
+ order_mock2.get_status.side_effect = ["open"]
+ order_mock3.get_status.side_effect = ["open", "closed"]
+
+ order_mock1.trade = mock.Mock()
+ order_mock2.trade = mock.Mock()
+ order_mock3.trade = mock.Mock()
+
+ m.follow_orders(sleep=sleep)
+
+ order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
+ order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
+ self.assertEqual(2, order_mock1.trade.update_order.call_count)
+ self.assertEqual(3, order_mock1.get_status.call_count)
+
+ order_mock2.trade.update_order.assert_any_call(order_mock2, 1)
+ self.assertEqual(1, order_mock2.trade.update_order.call_count)
+ self.assertEqual(1, order_mock2.get_status.call_count)
+
+ order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
+ self.assertEqual(1, order_mock3.trade.update_order.call_count)
+ self.assertEqual(2, order_mock3.get_status.call_count)
+ m.report.log_stage.assert_called()
+ calls = [
+ mock.call("follow_orders_begin"),
+ mock.call("follow_orders_tick_1"),
+ mock.call("follow_orders_tick_2"),
+ mock.call("follow_orders_tick_3"),
+ mock.call("follow_orders_end"),
+ ]
+ m.report.log_stage.assert_has_calls(calls)
+ m.report.log_orders.assert_called()
+ self.assertEqual(3, m.report.log_orders.call_count)
+ calls = [
+ mock.call([order_mock1, order_mock2], tick=1),
+ mock.call([order_mock1, order_mock3], tick=2),
+ mock.call([order_mock1, order_mock3], tick=3),
+ ]
+ m.report.log_orders.assert_has_calls(calls)
+ calls = [
+ mock.call(order_mock1, 3, finished=True),
+ mock.call(order_mock3, 3, finished=True),
+ ]
+ m.report.log_order.assert_has_calls(calls)
+
+ if sleep is None:
+ if debug:
+ m.report.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
+ time_mock.assert_called_with(7)
+ else:
+ time_mock.assert_called_with(30)
+ else:
+ time_mock.assert_called_with(sleep)
+
+ @mock.patch.object(market.BalanceStore, "fetch_balances")
+ def test_move_balance(self, fetch_balances):
+ for debug in [True, False]:
+ with self.subTest(debug=debug),\
+ mock.patch("market.ReportStore"):
+ m = market.Market(self.ccxt, debug=debug)
+
+ 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", m)
+
+ 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", m)
+
+ 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", m)
+
+ m.trades.all = [trade1, trade2, trade3]
+ balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
+ balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
+ balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
+ m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
+
+ m.move_balances()
+
+ fetch_balances.assert_called_with()
+ m.report.log_move_balances.assert_called_once()
+
+ if debug:
+ m.report.log_debug_action.assert_called()
+ self.assertEqual(3, m.report.log_debug_action.call_count)
+ else:
+ self.ccxt.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
+ self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
+ self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
+
+ def test_store_report(self):
+
+ file_open = mock.mock_open()
+ with self.subTest(file=None), mock.patch("market.open", file_open):
+ m = market.Market(self.ccxt, user_id=1)
+ m.store_report()
+ file_open.assert_not_called()
+
+ file_open = mock.mock_open()
+ m = market.Market(self.ccxt, report_path="present", user_id=1)
+ with self.subTest(file="present"),\
+ mock.patch("market.open", file_open),\
+ mock.patch.object(m, "report") as report,\
+ mock.patch.object(market, "datetime") as time_mock:
+
+ time_mock.now.return_value = datetime.datetime(2018, 2, 25)
+ report.to_json.return_value = "json_content"
+
+ m.store_report()
+
+ file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
+ file_open().write.assert_called_once_with("json_content")
+ m.report.to_json.assert_called_once_with()
+
+ m = market.Market(self.ccxt, report_path="error", user_id=1)
+ with self.subTest(file="error"),\
+ mock.patch("market.open") as file_open,\
+ mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ file_open.side_effect = FileNotFoundError
+
+ m.store_report()
+
+ self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
+
+ def test_print_orders(self):
+ m = market.Market(self.ccxt)
+ 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()
+
+ 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")
+
+ def test_print_balances(self):
+ m = market.Market(self.ccxt)
+
+ with mock.patch.object(m.balances, "in_currency") as in_currency,\
+ mock.patch.object(m.report, "log_stage") as log_stage,\
+ mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
+ mock.patch.object(m.report, "print_log") as print_log:
+
+ in_currency.return_value = {
+ "BTC": portfolio.Amount("BTC", "0.65"),
+ "ETH": portfolio.Amount("BTC", "0.3"),
+ }
+
+ m.print_balances()
+
+ 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")),
+ ])
+
+ @mock.patch("market.Processor.process")
+ @mock.patch("market.ReportStore.log_error")
+ @mock.patch("market.Market.store_report")
+ def test_process(self, store_report, log_error, process):
+ m = market.Market(self.ccxt)
+ with self.subTest(before=False, after=False):
+ m.process(None)
+
+ process.assert_not_called()
+ store_report.assert_called_once()
+ log_error.assert_not_called()
+
+ process.reset_mock()
+ log_error.reset_mock()
+ store_report.reset_mock()
+ with self.subTest(before=True, after=False):
+ m.process(None, before=True)
+
+ process.assert_called_once_with("sell_all", steps="before")
+ store_report.assert_called_once()
+ log_error.assert_not_called()
+
+ process.reset_mock()
+ log_error.reset_mock()
+ store_report.reset_mock()
+ with self.subTest(before=False, after=True):
+ m.process(None, after=True)
+
+ process.assert_called_once_with("sell_all", steps="after")
+ store_report.assert_called_once()
+ log_error.assert_not_called()
+
+ 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)
+
+ process.assert_has_calls([
+ mock.call("sell_all", steps="before"),
+ mock.call("sell_all", steps="after"),
+ ])
+ 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"])
+
+ process.assert_not_called()
+ log_error.assert_not_called()
+ 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()
+
+ log_error.reset_mock()
+ store_report.reset_mock()
+ with self.subTest(unhandled_exception=True):
+ process.side_effect = Exception("bouh")
+
+ m.process(None, before=True)
+ log_error.assert_called_with("market_process", exception=mock.ANY)
+ store_report.assert_called_once()
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class TradeStoreTest(WebMockTestCase):
+ def test_compute_trades(self):
+ self.m.balances.currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
+
+ values_in_base = {
+ "XMR": portfolio.Amount("BTC", D("0.9")),
+ "DASH": portfolio.Amount("BTC", D("0.4")),
+ "XVG": portfolio.Amount("BTC", D("-0.5")),
+ "BTC": portfolio.Amount("BTC", D("0.5")),
+ }
+ new_repartition = {
+ "DASH": portfolio.Amount("BTC", D("0.5")),
+ "XVG": portfolio.Amount("BTC", D("0.1")),
+ "BTC": portfolio.Amount("BTC", D("0.4")),
+ "ETH": portfolio.Amount("BTC", D("0.3")),
+ }
+ side_effect = [
+ (True, 1),
+ (False, 2),
+ (False, 3),
+ (True, 4),
+ (True, 5)
+ ]
+
+ with mock.patch.object(market.TradeStore, "trade_if_matching") as trade_if_matching:
+ trade_store = market.TradeStore(self.m)
+ trade_if_matching.side_effect = side_effect
+
+ trade_store.compute_trades(values_in_base,
+ new_repartition, only="only")
+
+ self.assertEqual(5, trade_if_matching.call_count)
+ self.assertEqual(3, len(trade_store.all))
+ self.assertEqual([1, 4, 5], trade_store.all)
+ self.m.report.log_trades.assert_called_with(side_effect, "only")
+
+ def test_trade_if_matching(self):
+
+ with self.subTest(only="nope"):
+ trade_store = market.TradeStore(self.m)
+ result = trade_store.trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="nope")
+ self.assertEqual(False, result[0])
+ self.assertIsInstance(result[1], portfolio.Trade)
+
+ with self.subTest(only=None):
+ trade_store = market.TradeStore(self.m)
+ result = trade_store.trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only=None)
+ self.assertEqual(True, result[0])
+
+ with self.subTest(only="acquire"):
+ trade_store = market.TradeStore(self.m)
+ result = trade_store.trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="acquire")
+ self.assertEqual(True, result[0])
+
+ with self.subTest(only="dispose"):
+ trade_store = market.TradeStore(self.m)
+ result = trade_store.trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="dispose")
+ self.assertEqual(False, result[0])
+
+ def test_prepare_orders(self):
+ trade_store = market.TradeStore(self.m)
+
+ trade_mock1 = mock.Mock()
+ trade_mock2 = mock.Mock()
+ trade_mock3 = mock.Mock()
+
+ trade_mock1.prepare_order.return_value = 1
+ trade_mock2.prepare_order.return_value = 2
+ trade_mock3.prepare_order.return_value = 3
+
+ trade_mock1.pending = True
+ trade_mock2.pending = True
+ trade_mock3.pending = False
+
+ trade_store.all.append(trade_mock1)
+ trade_store.all.append(trade_mock2)
+ trade_store.all.append(trade_mock3)
+
+ trade_store.prepare_orders()
+ trade_mock1.prepare_order.assert_called_with(compute_value="default")
+ trade_mock2.prepare_order.assert_called_with(compute_value="default")
+ trade_mock3.prepare_order.assert_not_called()
+ self.m.report.log_orders.assert_called_once_with([1, 2], None, "default")
+
+ self.m.report.log_orders.reset_mock()
+
+ trade_store.prepare_orders(compute_value="bla")
+ trade_mock1.prepare_order.assert_called_with(compute_value="bla")
+ trade_mock2.prepare_order.assert_called_with(compute_value="bla")
+ self.m.report.log_orders.assert_called_once_with([1, 2], None, "bla")
+
+ trade_mock1.prepare_order.reset_mock()
+ trade_mock2.prepare_order.reset_mock()
+ self.m.report.log_orders.reset_mock()
+
+ trade_mock1.action = "foo"
+ trade_mock2.action = "bar"
+ trade_store.prepare_orders(only="bar")
+ trade_mock1.prepare_order.assert_not_called()
+ trade_mock2.prepare_order.assert_called_with(compute_value="default")
+ self.m.report.log_orders.assert_called_once_with([2], "bar", "default")
+
+ def test_print_all_with_order(self):
+ trade_mock1 = mock.Mock()
+ trade_mock2 = mock.Mock()
+ trade_mock3 = mock.Mock()
+ trade_store = market.TradeStore(self.m)
+ trade_store.all = [trade_mock1, trade_mock2, trade_mock3]
+
+ trade_store.print_all_with_order()
+
+ trade_mock1.print_with_order.assert_called()
+ trade_mock2.print_with_order.assert_called()
+ trade_mock3.print_with_order.assert_called()
+
+ def test_run_orders(self):
+ with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ order_mock3 = mock.Mock()
+ trade_store = market.TradeStore(self.m)
+
+ all_orders.return_value = [order_mock1, order_mock2, order_mock3]
+
+ trade_store.run_orders()
+
+ all_orders.assert_called_with(state="pending")
+
+ order_mock1.run.assert_called()
+ order_mock2.run.assert_called()
+ order_mock3.run.assert_called()
+
+ self.m.report.log_stage.assert_called_with("run_orders")
+ self.m.report.log_orders.assert_called_with([order_mock1, order_mock2,
+ order_mock3])
+
+ def test_all_orders(self):
+ trade_mock1 = mock.Mock()
+ trade_mock2 = mock.Mock()
+
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ order_mock3 = mock.Mock()
+
+ trade_mock1.orders = [order_mock1, order_mock2]
+ trade_mock2.orders = [order_mock3]
+
+ order_mock1.status = "pending"
+ order_mock2.status = "open"
+ order_mock3.status = "open"
+
+ trade_store = market.TradeStore(self.m)
+ trade_store.all.append(trade_mock1)
+ trade_store.all.append(trade_mock2)
+
+ orders = trade_store.all_orders()
+ self.assertEqual(3, len(orders))
+
+ open_orders = trade_store.all_orders(state="open")
+ self.assertEqual(2, len(open_orders))
+ self.assertEqual([order_mock2, order_mock3], open_orders)
+
+ def test_update_all_orders_status(self):
+ with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ order_mock3 = mock.Mock()
+
+ all_orders.return_value = [order_mock1, order_mock2, order_mock3]
+
+ trade_store = market.TradeStore(self.m)
+
+ trade_store.update_all_orders_status()
+ all_orders.assert_called_with(state="open")
+
+ order_mock1.get_status.assert_called()
+ order_mock2.get_status.assert_called()
+ order_mock3.get_status.assert_called()
+
+ def test_close_trades(self):
+ trade_mock1 = mock.Mock()
+ trade_mock2 = mock.Mock()
+ trade_mock3 = mock.Mock()
+
+ trade_store = market.TradeStore(self.m)
+
+ trade_store.all.append(trade_mock1)
+ trade_store.all.append(trade_mock2)
+ trade_store.all.append(trade_mock3)
+
+ trade_store.close_trades()
+
+ trade_mock1.close.assert_called_once_with()
+ trade_mock2.close.assert_called_once_with()
+ trade_mock3.close.assert_called_once_with()
+
+ def test_pending(self):
+ trade_mock1 = mock.Mock()
+ trade_mock1.pending = True
+ trade_mock2 = mock.Mock()
+ trade_mock2.pending = True
+ trade_mock3 = mock.Mock()
+ trade_mock3.pending = False
+
+ trade_store = market.TradeStore(self.m)
+
+ trade_store.all.append(trade_mock1)
+ trade_store.all.append(trade_mock2)
+ trade_store.all.append(trade_mock3)
+
+ self.assertEqual([trade_mock1, trade_mock2], trade_store.pending)
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class BalanceStoreTest(WebMockTestCase):
+ def setUp(self):
+ super(BalanceStoreTest, self).setUp()
+
+ self.fetch_balance = {
+ "ETC": {
+ "exchange_free": 0,
+ "exchange_used": 0,
+ "exchange_total": 0,
+ "margin_total": 0,
+ },
+ "USDT": {
+ "exchange_free": D("6.0"),
+ "exchange_used": D("1.2"),
+ "exchange_total": D("7.2"),
+ "margin_total": 0,
+ },
+ "XVG": {
+ "exchange_free": 16,
+ "exchange_used": 0,
+ "exchange_total": 16,
+ "margin_total": 0,
+ },
+ "XMR": {
+ "exchange_free": 0,
+ "exchange_used": 0,
+ "exchange_total": 0,
+ "margin_total": D("-1.0"),
+ "margin_free": 0,
+ },
+ }
+
+ def test_in_currency(self):
+ self.m.get_ticker.return_value = {
+ "bid": D("0.09"),
+ "ask": D("0.11"),
+ "average": D("0.1"),
+ }
+
+ balance_store = market.BalanceStore(self.m)
+ balance_store.all = {