import portfolio
import unittest
+from decimal import Decimal as D
from unittest import mock
class AmountTest(unittest.TestCase):
def test_values(self):
- amount = portfolio.Amount("BTC", 0.65)
- self.assertEqual(0.65, amount.value)
+ amount = portfolio.Amount("BTC", "0.65")
+ self.assertEqual(D("0.65"), amount.value)
self.assertEqual("BTC", amount.currency)
- amount = portfolio.Amount("BTC", 10, int_val=2000000000000000)
- self.assertEqual(0.002, amount.value)
-
def test_in_currency(self):
amount = portfolio.Amount("ETC", 10)
with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
ticker_mock.return_value = {
- "average": 0.3,
+ "bid": D("0.2"),
+ "ask": D("0.4"),
+ "average": D("0.3"),
"foo": "bar",
}
converted_amount = amount.in_currency("ETH", None)
- self.assertEqual(3.0, converted_amount.value)
+ self.assertEqual(D("3.0"), converted_amount.value)
self.assertEqual("ETH", converted_amount.currency)
self.assertEqual(amount, converted_amount.linked_to)
self.assertEqual("bar", converted_amount.ticker["foo"])
+ converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
+ self.assertEqual(D("2"), converted_amount.value)
+
+ converted_amount = amount.in_currency("ETH", None, compute_value="ask")
+ self.assertEqual(D("4"), converted_amount.value)
+
+ converted_amount = amount.in_currency("ETH", None, rate=D("0.02"))
+ self.assertEqual(D("0.2"), converted_amount.value)
+
def test__abs(self):
amount = portfolio.Amount("SC", -120)
self.assertEqual(120, abs(amount).value)
self.assertEqual("SC", abs(amount).currency)
def test__add(self):
- amount1 = portfolio.Amount("XVG", 12.9)
- amount2 = portfolio.Amount("XVG", 13.1)
+ amount1 = portfolio.Amount("XVG", "12.9")
+ amount2 = portfolio.Amount("XVG", "13.1")
self.assertEqual(26, (amount1 + amount2).value)
self.assertEqual("XVG", (amount1 + amount2).currency)
- amount3 = portfolio.Amount("ETH", 1.6)
+ amount3 = portfolio.Amount("ETH", "1.6")
with self.assertRaises(Exception):
amount1 + amount3
self.assertEqual(amount1, amount1 + amount4)
def test__radd(self):
- amount = portfolio.Amount("XVG", 12.9)
+ amount = portfolio.Amount("XVG", "12.9")
self.assertEqual(amount, 0 + amount)
with self.assertRaises(Exception):
4 + amount
def test__sub(self):
- amount1 = portfolio.Amount("XVG", 13.3)
- amount2 = portfolio.Amount("XVG", 13.1)
+ amount1 = portfolio.Amount("XVG", "13.3")
+ amount2 = portfolio.Amount("XVG", "13.1")
- self.assertEqual(0.2, (amount1 - amount2).value)
+ self.assertEqual(D("0.2"), (amount1 - amount2).value)
self.assertEqual("XVG", (amount1 - amount2).currency)
- amount3 = portfolio.Amount("ETH", 1.6)
+ amount3 = portfolio.Amount("ETH", "1.6")
with self.assertRaises(Exception):
amount1 - amount3
amount4 = portfolio.Amount("ETH", 0.0)
self.assertEqual(amount1, amount1 - amount4)
- def test__int(self):
- amount = portfolio.Amount("XMR", 0.1)
- self.assertEqual(100000000000000000, int(amount))
-
def test__mul(self):
amount = portfolio.Amount("XEM", 11)
- self.assertEqual(38.5, (amount * 3.5).value)
- self.assertEqual(33, (amount * 3).value)
+ self.assertEqual(D("38.5"), (amount * D("3.5")).value)
+ self.assertEqual(D("33"), (amount * 3).value)
with self.assertRaises(Exception):
amount * amount
def test__rmul(self):
amount = portfolio.Amount("XEM", 11)
- self.assertEqual(38.5, (3.5 * amount).value)
- self.assertEqual(33, (3 * amount).value)
+ self.assertEqual(D("38.5"), (D("3.5") * amount).value)
+ self.assertEqual(D("33"), (3 * amount).value)
def test__floordiv(self):
amount = portfolio.Amount("XEM", 11)
- self.assertEqual(5.5, (amount // 2).value)
- with self.assertRaises(TypeError):
- amount // 2.5
- self.assertEqual(1571428571428571428, (amount // 7)._value)
+ self.assertEqual(D("5.5"), (amount / 2).value)
+ self.assertEqual(D("4.4"), (amount / D("2.5")).value)
def test__div(self):
amount = portfolio.Amount("XEM", 11)
- with self.assertRaises(TypeError):
- amount / 2.5
- self.assertEqual(5.5, (amount / 2).value)
- self.assertEqual(1571428571428571428, (amount / 7)._value)
+ self.assertEqual(D("5.5"), (amount / 2).value)
+ self.assertEqual(D("4.4"), (amount / D("2.5")).value)
def test__lt(self):
amount1 = portfolio.Amount("BTD", 11.3)
"info": "bar",
"used": "baz",
"total": "bazz",
+ "ETC": {
+ "free": 0.0,
+ "used": 0.0,
+ "total": 0.0
+ },
"USDT": {
"free": 6.0,
"used": 1.2,
@mock.patch.object(portfolio.Trade, "get_ticker")
def test_in_currency(self, get_ticker):
portfolio.Balance.known_balances = {
- "BTC": portfolio.Balance("BTC", 0.65, 0.35, 0.30),
+ "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
"ETH": portfolio.Balance("ETH", 3, 3, 0),
}
market = mock.Mock()
get_ticker.return_value = {
- "bid": 0.09,
- "ask": 0.11,
- "average": 0.1,
+ "bid": D("0.09"),
+ "ask": D("0.11"),
+ "average": D("0.1"),
}
amounts = portfolio.Balance.in_currency("BTC", market)
self.assertEqual("BTC", amounts["ETH"].currency)
- self.assertEqual(0.65, amounts["BTC"].value)
- self.assertEqual(0.30, amounts["ETH"].value)
+ self.assertEqual(D("0.65"), amounts["BTC"].value)
+ self.assertEqual(D("0.30"), amounts["ETH"].value)
- amounts = portfolio.Balance.in_currency("BTC", market, action="bid")
- self.assertEqual(0.65, amounts["BTC"].value)
- self.assertEqual(0.27, amounts["ETH"].value)
+ amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid")
+ self.assertEqual(D("0.65"), amounts["BTC"].value)
+ self.assertEqual(D("0.27"), amounts["ETH"].value)
- amounts = portfolio.Balance.in_currency("BTC", market, action="bid", type="used")
- self.assertEqual(0.30, amounts["BTC"].value)
+ amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="used")
+ self.assertEqual(D("0.30"), amounts["BTC"].value)
self.assertEqual(0, amounts["ETH"].value)
def test_currencies(self):
portfolio.Balance.known_balances = {
- "BTC": portfolio.Balance("BTC", 0.65, 0.35, 0.30),
+ "BTC": portfolio.Balance("BTC", "0.65", "0.35", "0.30"),
"ETH": portfolio.Balance("ETH", 3, 3, 0),
}
self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
portfolio.Balance.fetch_balances(portfolio.market)
self.assertNotIn("XMR", portfolio.Balance.currencies())
- self.assertEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
+ self.assertListEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
+
+ portfolio.Balance.known_balances["ETC"] = portfolio.Balance("ETC", "1", "0", "1")
+ portfolio.Balance.fetch_balances(portfolio.market)
+ self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total)
+ self.assertListEqual(["USDT", "XVG", "ETC"], list(portfolio.Balance.currencies()))
@mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
@mock.patch.object(portfolio.market, "fetch_balance")
"BTC": 2600,
}
- amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", 10.1))
+ amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1"))
self.assertIn("XEM", portfolio.Balance.currencies())
- self.assertEqual(2.6, amounts["BTC"].value)
- self.assertEqual(7.5, amounts["XEM"].value)
+ self.assertEqual(D("2.6"), amounts["BTC"].value)
+ self.assertEqual(D("7.5"), amounts["XEM"].value)
@mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand")
@mock.patch.object(portfolio.Trade, "get_ticker")
"XEM": 7500,
"BTC": 2500,
}
- get_ticker.side_effect = [
- { "average": 0.0001 },
- { "average": 0.000001 }
- ]
+ 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_balance.return_value = {
"USDT": {
- "free": 10000.0,
- "used": 0.0,
- "total": 10000.0
+ "free": D("10000.0"),
+ "used": D("0.0"),
+ "total": D("10000.0")
},
"XVG": {
- "free": 10000.0,
- "used": 0.0,
- "total": 10000.0
+ "free": D("10000.0"),
+ "used": D("0.0"),
+ "total": D("10000.0")
},
}
portfolio.Balance.prepare_trades(market)
call = compute_trades.call_args
self.assertEqual(market, call[1]["market"])
self.assertEqual(1, call[0][0]["USDT"].value)
- self.assertEqual(0.01, call[0][0]["XVG"].value)
- self.assertEqual(0.2525, call[0][1]["BTC"].value)
- self.assertEqual(0.7575, call[0][1]["XEM"].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)
+
+ @unittest.skip("TODO")
+ def test_update_trades(self):
+ pass
def test__repr(self):
balance = portfolio.Balance("BTX", 3, 1, 2)
market = mock.Mock()
market.fetch_ticker.side_effect = [
{ "bid": 1, "ask": 3 },
- portfolio.ccxt.ExchangeError("foo"),
+ portfolio.ExchangeError("foo"),
{ "bid": 10, "ask": 40 },
- portfolio.ccxt.ExchangeError("foo"),
- portfolio.ccxt.ExchangeError("foo"),
+ portfolio.ExchangeError("foo"),
+ portfolio.ExchangeError("foo"),
]
ticker = portfolio.Trade.get_ticker("ETH", "ETC", market)
@unittest.skip("TODO")
def test_values_assertion(self):
- pass
+ value_from = Amount("BTC", "1.0")
+ value_from.linked_to = Amount("ETH", "10.0")
+ value_to = Amount("BTC", "1.0")
+ trade = portfolioTrade(value_from, value_to, "ETH")
+ self.assertEqual("BTC", trade.base_currency)
+ self.assertEqual("ETH", trade.currency)
+
+ with self.assertRaises(AssertionError):
+ portfolio.Trade(value_from, value_to, "ETC")
+ with self.assertRaises(AssertionError):
+ value_from.linked_to = None
+ portfolio.Trade(value_from, value_to, "ETH")
+ with self.assertRaises(AssertionError):
+ value_from.currency = "ETH"
+ portfolio.Trade(value_from, value_to, "ETH")
@unittest.skip("TODO")
def test_fetch_fees(self):
def test_follow_orders(self):
pass
+ @unittest.skip("TODO")
+ def test_compute_value(self):
+ pass
+
@unittest.skip("TODO")
def test__repr(self):
pass
def tearDown(self):
self.patcher.stop()
+class AcceptanceTest(unittest.TestCase):
+ import time
+
+ def setUp(self):
+ super(AcceptanceTest, self).setUp()
+
+ self.patchers = [
+ mock.patch.multiple(portfolio.Balance, known_balances={}),
+ mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
+ mock.patch.multiple(portfolio.Trade,
+ ticker_cache={},
+ ticker_cache_timestamp=self.time.time(),
+ fees_cache={},
+ trades={}),
+ mock.patch.multiple(portfolio.Computation,
+ computations=portfolio.Computation.computations)
+ ]
+ for patcher in self.patchers:
+ patcher.start()
+
+ def test_success_sell_only_necessary(self):
+ fetch_balance = {
+ "ETH": {
+ "free": D("1.0"),
+ "used": D("0.0"),
+ "total": D("1.0"),
+ },
+ "ETC": {
+ "free": D("4.0"),
+ "used": D("0.0"),
+ "total": D("4.0"),
+ },
+ "XVG": {
+ "free": D("1000.0"),
+ "used": D("0.0"),
+ "total": D("1000.0"),
+ },
+ }
+ repartition = {
+ "ETH": 2500,
+ "ETC": 2500,
+ "BTC": 4000,
+ "BTD": 500,
+ "USDT": 500,
+ }
+
+ def fetch_ticker(symbol):
+ if symbol == "ETH/BTC":
+ return {
+ "symbol": "ETH/BTC",
+ "bid": D("0.14"),
+ "ask": D("0.16")
+ }
+ if symbol == "ETC/BTC":
+ return {
+ "symbol": "ETC/BTC",
+ "bid": D("0.002"),
+ "ask": D("0.003")
+ }
+ if symbol == "XVG/BTC":
+ return {
+ "symbol": "XVG/BTC",
+ "bid": D("0.00003"),
+ "ask": D("0.00005")
+ }
+ if symbol == "BTD/BTC":
+ return {
+ "symbol": "BTD/BTC",
+ "bid": D("0.0008"),
+ "ask": D("0.0012")
+ }
+ if symbol == "USDT/BTC":
+ raise portfolio.ExchangeError
+ if symbol == "BTC/USDT":
+ return {
+ "symbol": "BTC/USDT",
+ "bid": D("14000"),
+ "ask": D("16000")
+ }
+ self.fail("Shouldn't have been called with {}".format(symbol))
+
+ market = mock.Mock()
+ market.fetch_balance.return_value = fetch_balance
+ market.fetch_ticker.side_effect = fetch_ticker
+ with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition):
+ # Action 1
+ portfolio.Balance.prepare_trades(market)
+
+ balances = portfolio.Balance.known_balances
+ self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
+ self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
+ self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
+
+
+ trades = portfolio.Trade.trades
+ 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)
+
+ self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["ETC"].value_from)
+ self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETC"].value_to)
+ self.assertEqual("buy", trades["ETC"].action)
+
+ self.assertNotIn("BTC", trades)
+
+ self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
+ self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["BTD"].value_to)
+ self.assertEqual("buy", trades["BTD"].action)
+
+ self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from)
+ self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["USDT"].value_to)
+ self.assertEqual("buy", trades["USDT"].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)
+
+ # Action 2
+ portfolio.Trade.prepare_orders(only="sell", compute_value=lambda x, y: x["bid"] * D("1.001"))
+
+ all_orders = portfolio.Trade.all_orders()
+ self.assertEqual(2, len(all_orders))
+ self.assertEqual(2, 3*all_orders[0].amount.value)
+ self.assertEqual(D("0.14014"), all_orders[0].rate)
+ self.assertEqual(1000, all_orders[1].amount.value)
+ self.assertEqual(D("0.00003003"), all_orders[1].rate)
+
+
+ def create_order(symbol, type, action, amount, price=None, account="exchange"):
+ self.assertEqual("limit", type)
+ if symbol == "ETH/BTC":
+ self.assertEqual("sell", action)
+ self.assertEqual(2, 3*amount)
+ self.assertEqual(D("0.14014"), price)
+ elif symbol == "XVG/BTC":
+ self.assertEqual("sell", action)
+ self.assertEqual(1000, amount)
+ self.assertEqual(D("0.00003003"), price)
+ else:
+ self.fail("I shouldn't have been called")
+
+ return {
+ "id": symbol,
+ }
+ market.create_order.side_effect = create_order
+
+ # Action 3
+ portfolio.Trade.run_orders()
+
+ self.assertEqual("open", all_orders[0].status)
+ self.assertEqual("open", all_orders[1].status)
+
+ market.fetch_order.return_value = { "status": "closed" }
+ with mock.patch.object(portfolio.time, "sleep") as sleep:
+ # Action 4
+ portfolio.Trade.follow_orders(verbose=False)
+
+ sleep.assert_called_with(30)
+
+ for order in all_orders:
+ self.assertEqual("closed", order.status)
+
+ fetch_balance = {
+ "ETH": {
+ "free": D("1.0") / 3,
+ "used": D("0.0"),
+ "total": D("1.0") / 3,
+ },
+ "BTC": {
+ "free": D("0.134"),
+ "used": D("0.0"),
+ "total": D("0.134"),
+ },
+ "ETC": {
+ "free": D("4.0"),
+ "used": D("0.0"),
+ "total": D("4.0"),
+ },
+ "XVG": {
+ "free": D("0.0"),
+ "used": D("0.0"),
+ "total": D("0.0"),
+ },
+ }
+ market.fetch_balance.return_value = fetch_balance
+
+ with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition):
+ # Action 5
+ portfolio.Balance.update_trades(market, only="buy", compute_value="average")
+
+ balances = portfolio.Balance.known_balances
+ self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
+ self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
+ self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
+ self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
+
+
+ trades = portfolio.Trade.trades
+ 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)
+
+ 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.assertNotIn("BTC", trades)
+
+ self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from)
+ self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["BTD"].value_to)
+ self.assertEqual("buy", trades["BTD"].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.04")), trades["XVG"].value_from)
+ self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["XVG"].value_to)
+ self.assertEqual("sell", trades["XVG"].action)
+
+ # Action 6
+ portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"])
+
+ all_orders = portfolio.Trade.all_orders(state="pending")
+ self.assertEqual(3, len(all_orders))
+ self.assertEqual(portfolio.Amount("ETC", D("38.5")/3), all_orders[0].amount)
+ self.assertEqual(D("0.003"), all_orders[0].rate)
+ self.assertEqual("buy", all_orders[0].action)
+
+ self.assertEqual(portfolio.Amount("BTD", D("24.25")/3), all_orders[1].amount)
+ self.assertEqual(D("0.0012"), all_orders[1].rate)
+ self.assertEqual("buy", all_orders[1].action)
+
+ self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[2].amount)
+ self.assertEqual(D("16000"), all_orders[2].rate)
+ self.assertEqual("sell", all_orders[2].action)
+
+ with mock.patch.object(portfolio.time, "sleep") as sleep:
+ # Action 7
+ portfolio.Trade.follow_orders(verbose=False)
+
+ sleep.assert_called_with(30)
+
+ def tearDown(self):
+ for patcher in self.patchers:
+ patcher.stop()
+
if __name__ == '__main__':
unittest.main()