+ amount2 = portfolio.Amount("USDT", 12000)
+ amount1.linked_to = amount2
+ self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
+
+ amount3 = portfolio.Amount("BTC", 0.1)
+ amount2.linked_to = amount3
+ self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
+
+class BalanceTest(WebMockTestCase):
+ def test_values(self):
+ balance = portfolio.Balance("BTC", {
+ "exchange_total": "0.65",
+ "exchange_free": "0.35",
+ "exchange_used": "0.30",
+ "margin_total": "-10",
+ "margin_borrowed": "-10",
+ "margin_free": "0",
+ "margin_position_type": "short",
+ "margin_borrowed_base_currency": "USDT",
+ "margin_liquidation_price": "1.20",
+ "margin_pending_gain": "10",
+ "margin_lending_fees": "0.4",
+ "margin_borrowed_base_price": "0.15",
+ })
+ self.assertEqual(portfolio.D("0.65"), balance.exchange_total.value)
+ self.assertEqual(portfolio.D("0.35"), balance.exchange_free.value)
+ self.assertEqual(portfolio.D("0.30"), balance.exchange_used.value)
+ self.assertEqual("BTC", balance.exchange_total.currency)
+ self.assertEqual("BTC", balance.exchange_free.currency)
+ self.assertEqual("BTC", balance.exchange_total.currency)
+
+ self.assertEqual(portfolio.D("-10"), balance.margin_total.value)
+ self.assertEqual(portfolio.D("-10"), balance.margin_borrowed.value)
+ self.assertEqual(portfolio.D("0"), balance.margin_free.value)
+ self.assertEqual("BTC", balance.margin_total.currency)
+ self.assertEqual("BTC", balance.margin_borrowed.currency)
+ self.assertEqual("BTC", balance.margin_free.currency)
+
+ self.assertEqual("BTC", balance.currency)
+
+ self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
+ self.assertEqual("USDT", balance.margin_lending_fees.currency)
+
+ 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", { "margin_total": 3,
+ "margin_borrowed": 1, "margin_free": 2 })
+ self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.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))
+
+class HelperTest(WebMockTestCase):
+ def test_get_ticker(self):
+ market = mock.Mock()
+ market.fetch_ticker.side_effect = [
+ { "bid": 1, "ask": 3 },
+ helper.ExchangeError("foo"),
+ { "bid": 10, "ask": 40 },
+ helper.ExchangeError("foo"),
+ helper.ExchangeError("foo"),
+ ]
+
+ ticker = helper.get_ticker("ETH", "ETC", market)
+ market.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 = helper.get_ticker("ETH", "XVG", market)
+ self.assertEqual(0.0625, ticker["average"])
+ self.assertTrue(ticker["inverted"])
+ self.assertIn("original", ticker)
+ self.assertEqual(10, ticker["original"]["bid"])
+
+ ticker = helper.get_ticker("XVG", "XMR", market)
+ self.assertIsNone(ticker)
+
+ market.fetch_ticker.assert_has_calls([
+ mock.call("ETH/ETC"),
+ mock.call("ETH/XVG"),
+ mock.call("XVG/ETH"),
+ mock.call("XVG/XMR"),
+ mock.call("XMR/XVG"),
+ ])
+
+ market2 = mock.Mock()
+ market2.fetch_ticker.side_effect = [
+ { "bid": 1, "ask": 3 },
+ { "bid": 1.2, "ask": 3.5 },
+ ]
+ ticker1 = helper.get_ticker("ETH", "ETC", market2)
+ ticker2 = helper.get_ticker("ETH", "ETC", market2)
+ ticker3 = helper.get_ticker("ETC", "ETH", market2)
+ market2.fetch_ticker.assert_called_once_with("ETH/ETC")
+ self.assertEqual(1, ticker1["bid"])
+ self.assertDictEqual(ticker1, ticker2)
+ self.assertDictEqual(ticker1, ticker3["original"])
+
+ ticker4 = helper.get_ticker("ETH", "ETC", market2, refresh=True)
+ ticker5 = helper.get_ticker("ETH", "ETC", market2)
+ self.assertEqual(1.2, ticker4["bid"])
+ self.assertDictEqual(ticker4, ticker5)
+
+ market3 = mock.Mock()
+ market3.fetch_ticker.side_effect = [
+ { "bid": 1, "ask": 3 },
+ { "bid": 1.2, "ask": 3.5 },
+ ]
+ ticker6 = helper.get_ticker("ETH", "ETC", market3)
+ helper.ticker_cache_timestamp -= 4
+ ticker7 = helper.get_ticker("ETH", "ETC", market3)
+ helper.ticker_cache_timestamp -= 2
+ ticker8 = helper.get_ticker("ETH", "ETC", market3)
+ self.assertDictEqual(ticker6, ticker7)
+ self.assertEqual(1.2, ticker8["bid"])
+
+ def test_fetch_fees(self):
+ market = mock.Mock()
+ market.fetch_fees.return_value = "Foo"
+ self.assertEqual("Foo", helper.fetch_fees(market))
+ market.fetch_fees.assert_called_once()
+ self.assertEqual("Foo", helper.fetch_fees(market))
+ market.fetch_fees.assert_called_once()
+
+ @mock.patch.object(portfolio.Portfolio, "repartition")
+ @mock.patch.object(helper, "get_ticker")
+ @mock.patch.object(portfolio.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, market):
+ 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
+
+ market = mock.Mock()
+ market.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")
+ },
+ }
+ helper.prepare_trades(market)
+ compute_trades.assert_called()
+
+ call = compute_trades.call_args
+ self.assertEqual(market, call[1]["market"])
+ 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)
+
+ @mock.patch.object(portfolio.Portfolio, "repartition")
+ @mock.patch.object(helper, "get_ticker")
+ @mock.patch.object(portfolio.TradeStore, "compute_trades")
+ def test_update_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, market):
+ 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
+
+ market = mock.Mock()
+ market.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")
+ },
+ }
+ helper.update_trades(market)
+ compute_trades.assert_called()
+
+ call = compute_trades.call_args
+ self.assertEqual(market, call[1]["market"])
+ 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)
+
+ @mock.patch.object(portfolio.Portfolio, "repartition")
+ @mock.patch.object(helper, "get_ticker")
+ @mock.patch.object(portfolio.TradeStore, "compute_trades")
+ def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
+ def _get_ticker(c1, c2, market):
+ 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
+
+ market = mock.Mock()
+ market.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")
+ },
+ }
+ helper.prepare_trades_to_sell_all(market)
+ repartition.assert_not_called()
+ compute_trades.assert_called()
+
+ call = compute_trades.call_args
+ self.assertEqual(market, call[1]["market"])
+ self.assertEqual(1, call[0][0]["USDT"].value)
+ self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
+ self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
+
+ @unittest.skip("TODO")
+ def test_follow_orders(self):
+ pass
+
+
+class TradeStoreTest(WebMockTestCase):
+ @unittest.skip("TODO")
+ def test_compute_trades(self):
+ pass
+
+ def test_prepare_orders(self):
+ trade_mock1 = mock.Mock()
+ trade_mock2 = mock.Mock()