def __floordiv__(self, value):
if not isinstance(value, (int, float, D)):
- raise TypeError("Amount may only be multiplied by integers")
+ raise TypeError("Amount may only be divided by numbers")
return Amount(self.currency, self.value / value)
def __truediv__(self, value):
else:
return "long"
- @property
- def filled_amount(self):
+ def filled_amount(self, in_base_currency=False):
filled_amount = 0
for order in self.orders:
- filled_amount += order.filled_amount
+ filled_amount += order.filled_amount(in_base_currency=in_base_currency)
return filled_amount
def update_order(self, order, tick):
if inverted:
ticker = ticker["original"]
rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
- # 0.1
delta_in_base = abs(self.value_from - self.value_to)
# 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)
if not inverted:
- currency = self.base_currency
+ base_currency = self.base_currency
# BTC
if self.action == "dispose":
- # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
- # At rate 1 Foo = 0.1 BTC
- value_from = self.value_from.linked_to
- # value_from = 100 FOO
- value_to = self.value_to.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
- # value_to = 10 FOO (1 BTC * 1/0.1)
- delta = abs(value_to - value_from)
- # delta = 90 FOO
- # Action: "sell" "90 FOO" at rate "0.1" "BTC" on "market"
-
- # Note: no rounding error possible: if we have value_to == 0, then delta == value_from
+ filled = self.filled_amount(in_base_currency=False)
+ delta = delta_in_base.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
+ # I have 10 BTC worth of FOO, and I want to sell 9 BTC
+ # worth of it, computed first with rate 10 FOO = 1 BTC.
+ # -> I "sell" "90" FOO at proposed rate "rate".
+
+ delta = delta - filled
+ # I already sold 60 FOO, 30 left
else:
- delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate)
- # I want to buy 9 / 0.1 FOO
- # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market"
+ filled = self.filled_amount(in_base_currency=True)
+ delta = (delta_in_base - filled).in_currency(self.currency, self.market, rate=1/rate)
+ # I want to buy 9 BTC worth of FOO, computed with rate
+ # 10 FOO = 1 BTC
+ # -> I "buy" "9 / rate" FOO at proposed rate "rate"
+
+ # I already bought 3 / rate FOO, 6 / rate left
else:
- currency = self.currency
+ base_currency = self.currency
# FOO
- delta = delta_in_base
- # sell:
- # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
- # At rate 1 Foo = 0.1 BTC
- # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market
- # buy:
- # I want to buy 9 / 0.1 FOO
- # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market"
- if self.value_to == 0:
- rate = self.value_from.linked_to.value / self.value_from.value
- # Recompute the rate to avoid any rounding error
+ if self.action == "dispose":
+ filled = self.filled_amount(in_base_currency=True)
+ # Base is FOO
+
+ delta = (delta_in_base.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
+ - filled).in_currency(self.base_currency, self.market, rate=1/rate)
+ # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
+ # computed at rate 1 Foo = 0.01 BTC
+ # Computation says I should sell it at 125 FOO / BTC
+ # -> delta_in_base = 9 BTC
+ # -> delta = (9 * 1/0.01 FOO) * 1/125 = 7.2 BTC
+ # Action: "buy" "7.2 BTC" at rate "125" "FOO" on market
+
+ # I already bought 300/125 BTC, only 600/125 left
+ else:
+ filled = self.filled_amount(in_base_currency=False)
+ # Base is FOO
+
+ delta = delta_in_base
+ # I have 1 BTC worth of FOO, and I want to buy 9 BTC worth of it
+ # At rate 100 Foo / BTC
+ # Computation says I should buy it at 125 FOO / BTC
+ # -> delta_in_base = 9 BTC
+ # Action: "sell" "9 BTC" at rate "125" "FOO" on market
+
+ delta = delta - filled
+ # I already sold 4 BTC, only 5 left
close_if_possible = (self.value_to == 0)
- if delta <= self.filled_amount:
- print("Less to do than already filled: {} <= {}".format(delta,
- self.filled_amount))
+ if delta <= 0:
+ print("Less to do than already filled: {}".format(delta))
return
self.orders.append(Order(self.order_action(inverted),
- delta - self.filled_amount, rate, currency, self.trade_type,
+ delta, rate, base_currency, self.trade_type,
self.market, self, close_if_possible=close_if_possible))
def __repr__(self):
def remaining_amount(self):
if self.status == "open":
self.fetch()
- return self.amount - self.filled_amount
+ return self.amount - self.filled_amount()
- @property
- def filled_amount(self):
+ def filled_amount(self, in_base_currency=False):
if self.status == "open":
self.fetch()
- filled_amount = Amount(self.amount.currency, 0)
+ filled_amount = 0
for mouvement in self.mouvements:
- filled_amount += mouvement.total
+ if in_base_currency:
+ filled_amount += mouvement.total_in_base
+ else:
+ filled_amount += mouvement.total
return filled_amount
def fetch_mouvements(self):
+import sys
import portfolio
import unittest
from decimal import Decimal as D
from io import StringIO
import helper
+limits = ["acceptance", "unit"]
+for test_type in limits:
+ if "--no{}".format(test_type) in sys.argv:
+ sys.argv.remove("--no{}".format(test_type))
+ limits.remove(test_type)
+ if "--only{}".format(test_type) in sys.argv:
+ sys.argv.remove("--only{}".format(test_type))
+ limits = [test_type]
+ break
+
class WebMockTestCase(unittest.TestCase):
import time
self.wm.stop()
super(WebMockTestCase, self).tearDown()
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class PortfolioTest(WebMockTestCase):
def fill_data(self):
if self.json_response is not None:
self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class AmountTest(WebMockTestCase):
def test_values(self):
amount = portfolio.Amount("BTC", "0.65")
self.assertEqual(D("5.5"), (amount / 2).value)
self.assertEqual(D("4.4"), (amount / D("2.5")).value)
+ with self.assertRaises(Exception):
+ amount / amount
+
def test__truediv(self):
amount = portfolio.Amount("XEM", 11)
amount2.linked_to = amount3
self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceTest(WebMockTestCase):
def test_values(self):
balance = portfolio.Balance("BTC", {
"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_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": 2, "margin_free": 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_borrowed": 1, "exchange_free": 2, "exchange_total": 2})
+ self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class HelperTest(WebMockTestCase):
def test_get_ticker(self):
market = mock.Mock()
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
-
-
+ @mock.patch.object(portfolio.time, "sleep")
+ @mock.patch.object(portfolio.TradeStore, "all_orders")
+ def test_follow_orders(self, all_orders, time_mock):
+ for verbose, debug, sleep in [
+ (True, False, None), (False, False, None),
+ (True, True, None), (True, False, 12),
+ (True, True, 12)]:
+ with self.subTest(sleep=sleep, debug=debug, verbose=verbose), \
+ mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ portfolio.TradeStore.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()
+
+ helper.follow_orders(verbose=verbose, 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)
+
+ if sleep is None:
+ if debug:
+ time_mock.assert_called_with(7)
+ else:
+ time_mock.assert_called_with(30)
+ else:
+ time_mock.assert_called_with(sleep)
+
+ if verbose:
+ self.assertNotEqual("", stdout_mock.getvalue())
+ else:
+ self.assertEqual("", stdout_mock.getvalue())
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeStoreTest(WebMockTestCase):
- @unittest.skip("TODO")
- def test_compute_trades(self):
- pass
+ @mock.patch.object(portfolio.BalanceStore, "currencies")
+ @mock.patch.object(portfolio.TradeStore, "add_trade_if_matching")
+ def test_compute_trades(self, add_trade_if_matching, currencies):
+ 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")),
+ }
+
+ portfolio.TradeStore.compute_trades(values_in_base,
+ new_repartition, only="only", market="market")
+
+ self.assertEqual(5, add_trade_if_matching.call_count)
+ add_trade_if_matching.assert_any_call(
+ portfolio.Amount("BTC", D("0.9")),
+ portfolio.Amount("BTC", 0),
+ "XMR", only="only", market="market"
+ )
+ add_trade_if_matching.assert_any_call(
+ portfolio.Amount("BTC", D("0.4")),
+ portfolio.Amount("BTC", D("0.5")),
+ "DASH", only="only", market="market"
+ )
+ add_trade_if_matching.assert_any_call(
+ portfolio.Amount("BTC", D("-0.5")),
+ portfolio.Amount("BTC", D("0")),
+ "XVG", only="only", market="market"
+ )
+ add_trade_if_matching.assert_any_call(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.1")),
+ "XVG", only="only", market="market"
+ )
+ add_trade_if_matching.assert_any_call(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="only", market="market"
+ )
+
+ def test_add_trade_if_matching(self):
+ result = portfolio.TradeStore.add_trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="nope", market="market"
+ )
+ self.assertEqual(0, len(portfolio.TradeStore.all))
+ self.assertEqual(False, result)
+
+ portfolio.TradeStore.all = []
+ result = portfolio.TradeStore.add_trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only=None, market="market"
+ )
+ self.assertEqual(1, len(portfolio.TradeStore.all))
+ self.assertEqual(True, result)
+
+ portfolio.TradeStore.all = []
+ result = portfolio.TradeStore.add_trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="acquire", market="market"
+ )
+ self.assertEqual(1, len(portfolio.TradeStore.all))
+ self.assertEqual(True, result)
+
+ portfolio.TradeStore.all = []
+ result = portfolio.TradeStore.add_trade_if_matching(
+ portfolio.Amount("BTC", D("0")),
+ portfolio.Amount("BTC", D("0.3")),
+ "ETH", only="dispose", market="market"
+ )
+ self.assertEqual(0, len(portfolio.TradeStore.all))
+ self.assertEqual(False, result)
def test_prepare_orders(self):
trade_mock1 = mock.Mock()
order_mock2.get_status.assert_called()
order_mock3.get_status.assert_called()
-
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceStoreTest(WebMockTestCase):
def setUp(self):
super(BalanceStoreTest, self).setUp()
repartition.return_value = {
"XEM": (D("0.75"), "long"),
"BTC": (D("0.26"), "long"),
+ "DASH": (D("0.10"), "short"),
}
- amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "10.1"))
+ amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
self.assertIn("XEM", portfolio.BalanceStore.currencies())
self.assertEqual(D("2.6"), amounts["BTC"].value)
self.assertEqual(D("7.5"), amounts["XEM"].value)
+ self.assertEqual(D("-1.0"), amounts["DASH"].value)
def test_currencies(self):
portfolio.BalanceStore.all = {
}
self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class ComputationTest(WebMockTestCase):
def test_compute_value(self):
compute = mock.Mock()
compute.assert_called_with("foo", "bid")
+@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeTest(WebMockTestCase):
def test_values_assertion(self):
value_from = portfolio.Amount("BTC", "1.0")
value_from.linked_to = portfolio.Amount("BTC", "1.0")
- value_to = portfolio.Amount("BTC", "1.0")
+ value_to = portfolio.Amount("BTC", "2.0")
trade = portfolio.Trade(value_from, value_to, "BTC")
self.assertIsNone(trade.action)
trade = portfolio.Trade(value_from, value_to, "ETH")
order1 = mock.Mock()
- order1.filled_amount = portfolio.Amount("ETH", "0.3")
+ order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")
order2 = mock.Mock()
- order2.filled_amount = portfolio.Amount("ETH", "0.01")
+ order2.filled_amount.return_value = portfolio.Amount("ETH", "0.01")
trade.orders.append(order1)
trade.orders.append(order2)
- self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount)
+ self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount())
+ order1.filled_amount.assert_called_with(in_base_currency=False)
+ order2.filled_amount.assert_called_with(in_base_currency=False)
- @unittest.skip("TODO")
- def test_prepare_order(self):
- pass
+ self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
+ order1.filled_amount.assert_called_with(in_base_currency=False)
+ order2.filled_amount.assert_called_with(in_base_currency=False)
+
+ self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
+ order1.filled_amount.assert_called_with(in_base_currency=True)
+ order2.filled_amount.assert_called_with(in_base_currency=True)
+
+ @mock.patch.object(helper, "get_ticker")
+ @mock.patch.object(portfolio.Computation, "compute_value")
+ @mock.patch.object(portfolio.Trade, "filled_amount")
+ @mock.patch.object(portfolio, "Order")
+ def test_prepare_order(self, Order, filled_amount, compute_value, get_ticker):
+ Order.return_value = "Order"
+
+ with self.subTest(desc="Nothing to do"):
+ 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", "10")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order()
+
+ filled_amount.assert_not_called()
+ compute_value.assert_not_called()
+ self.assertEqual(0, len(trade.orders))
+ Order.assert_not_called()
+
+ get_ticker.return_value = { "inverted": False }
+ with self.subTest(desc="Already filled"), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ filled_amount.return_value = portfolio.Amount("FOO", "100")
+ 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", "0")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order()
+
+ filled_amount.assert_called_with(in_base_currency=False)
+ compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
+ self.assertEqual(0, len(trade.orders))
+ self.assertRegex(stdout_mock.getvalue(), "Less to do than already filled: ")
+ Order.assert_not_called()
+
+ with self.subTest(action="dispose", inverted=False):
+ 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", market="market")
+
+ trade.prepare_order()
+
+ filled_amount.assert_called_with(in_base_currency=False)
+ compute_value.assert_called_with(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", "market",
+ trade, close_if_possible=False)
+
+ with self.subTest(action="acquire", inverted=False):
+ filled_amount.return_value = portfolio.Amount("BTC", "3")
+ compute_value.return_value = D("0.125")
+
+ value_from = portfolio.Amount("BTC", "1")
+ value_from.rate = D("0.1")
+ value_from.linked_to = portfolio.Amount("FOO", "10")
+ value_to = portfolio.Amount("BTC", "10")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order()
+
+ filled_amount.assert_called_with(in_base_currency=True)
+ compute_value.assert_called_with(get_ticker.return_value, "buy", compute_value="default")
+ self.assertEqual(1, len(trade.orders))
+
+ Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
+ D("0.125"), "BTC", "long", "market",
+ trade, close_if_possible=False)
+
+ with self.subTest(close_if_possible=True):
+ filled_amount.return_value = portfolio.Amount("FOO", "0")
+ 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", "0")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order()
+
+ filled_amount.assert_called_with(in_base_currency=False)
+ compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
+ self.assertEqual(1, len(trade.orders))
+ Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
+ D("0.125"), "BTC", "long", "market",
+ trade, close_if_possible=True)
+
+ get_ticker.return_value = { "inverted": True, "original": {} }
+ with self.subTest(action="dispose", inverted=True):
+ filled_amount.return_value = portfolio.Amount("FOO", "300")
+ compute_value.return_value = D("125")
+
+ value_from = portfolio.Amount("BTC", "10")
+ value_from.rate = D("0.01")
+ value_from.linked_to = portfolio.Amount("FOO", "1000")
+ value_to = portfolio.Amount("BTC", "1")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order(compute_value="foo")
+
+ filled_amount.assert_called_with(in_base_currency=True)
+ compute_value.assert_called_with(get_ticker.return_value["original"], "buy", compute_value="foo")
+ self.assertEqual(1, len(trade.orders))
+ Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
+ D("125"), "FOO", "long", "market",
+ trade, close_if_possible=False)
+
+ with self.subTest(action="acquire", inverted=True):
+ filled_amount.return_value = portfolio.Amount("BTC", "4")
+ compute_value.return_value = D("125")
+
+ value_from = portfolio.Amount("BTC", "1")
+ value_from.rate = D("0.01")
+ value_from.linked_to = portfolio.Amount("FOO", "100")
+ value_to = portfolio.Amount("BTC", "10")
+ trade = portfolio.Trade(value_from, value_to, "FOO", market="market")
+
+ trade.prepare_order(compute_value="foo")
+
+ filled_amount.assert_called_with(in_base_currency=False)
+ compute_value.assert_called_with(get_ticker.return_value["original"], "sell", compute_value="foo")
+ self.assertEqual(1, len(trade.orders))
+ Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
+ D("125"), "FOO", "long", "market",
+ trade, close_if_possible=False)
+
+
+ @mock.patch.object(portfolio.Trade, "prepare_order")
+ def test_update_order(self, prepare_order):
+ order_mock = mock.Mock()
+ new_order_mock = mock.Mock()
+
+ value_from = portfolio.Amount("BTC", "0.5")
+ 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
+
+ for i in [0, 1, 3, 4, 6]:
+ with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, i)
+ 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()
+ trade.orders = []
+
+ with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, 2)
+ order_mock.cancel.assert_called()
+ 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()
+ trade.orders = []
+
+ with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, 5)
+ order_mock.cancel.assert_called()
+ 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()
+ trade.orders = []
+
+ with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, 7)
+ order_mock.cancel.assert_called()
+ new_order_mock.run.assert_called()
+ 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()
+ trade.orders = []
+
+ for i in [10, 13, 16]:
+ with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, i)
+ order_mock.cancel.assert_called()
+ new_order_mock.run.assert_called()
+ 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()
+ trade.orders = []
+
+ for i in [8, 9, 11, 12]:
+ with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ trade.update_order(order_mock, i)
+ 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()
+ trade.orders = []
- @unittest.skip("TODO")
- def test_update_order(self):
- pass
@mock.patch('sys.stdout', new_callable=StringIO)
def test_print_with_order(self, mock_stdout):
self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
+@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
class AcceptanceTest(WebMockTestCase):
@unittest.expectedFailure
def test_success_sell_only_necessary(self):