]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - test.py
Various fixes/improvements
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
diff --git a/test.py b/test.py
index 778fc14961cf27011a352624936dcbe0b7780a2f..141c9e062d9cd6ea9a7ecfdb98bcdf3bdef93862 100644 (file)
--- a/test.py
+++ b/test.py
@@ -35,10 +35,6 @@ class WebMockTestCase(unittest.TestCase):
                 mock.patch.multiple(portfolio.Portfolio, last_date=None, data=None, liquidities={}),
                 mock.patch.multiple(portfolio.Computation,
                     computations=portfolio.Computation.computations),
-                mock.patch.multiple(market.Market,
-                    fees_cache={},
-                    ticker_cache={},
-                    ticker_cache_timestamp=self.time.time()),
                 ]
         for patcher in self.patchers:
             patcher.start()
@@ -472,8 +468,9 @@ class BalanceTest(WebMockTestCase):
             "exchange_free": "0.35",
             "exchange_used": "0.30",
             "margin_total": "-10",
-            "margin_borrowed": "-10",
-            "margin_free": "0",
+            "margin_borrowed": "10",
+            "margin_available": "0",
+            "margin_in_position": "0",
             "margin_position_type": "short",
             "margin_borrowed_base_currency": "USDT",
             "margin_liquidation_price": "1.20",
@@ -489,11 +486,11 @@ class BalanceTest(WebMockTestCase):
         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(portfolio.D("10"), balance.margin_borrowed.value)
+        self.assertEqual(portfolio.D("0"), balance.margin_available.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.margin_available.currency)
 
         self.assertEqual("BTC", balance.currency)
 
@@ -511,10 +508,10 @@ class BalanceTest(WebMockTestCase):
         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))
+            "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_free": 2 })
+        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,
@@ -524,8 +521,8 @@ class BalanceTest(WebMockTestCase):
         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))
+            "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 })
@@ -536,7 +533,7 @@ class BalanceTest(WebMockTestCase):
         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_free"])
+        self.assertEqual(D(0), as_json["margin_available"])
         self.assertEqual(D(0), as_json["margin_borrowed"])
 
 @unittest.skipUnless("unit" in limits, "Unit skipped")
@@ -583,77 +580,69 @@ class MarketTest(WebMockTestCase):
         m = market.Market.from_config({"key": "key", "secred": "secret"}, debug=True)
         self.assertEqual(True, m.debug)
 
-    def test_get_ticker(self):
-        m = market.Market(self.ccxt)
-        self.ccxt.fetch_ticker.side_effect = [
-                { "bid": 1, "ask": 3 },
-                market.ExchangeError("foo"),
-                { "bid": 10, "ask": 40 },
-                market.ExchangeError("foo"),
-                market.ExchangeError("foo"),
+    def test_get_tickers(self):
+        self.ccxt.fetch_tickers.side_effect = [
+                "tickers",
+                market.NotSupported
                 ]
 
-        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"])
-
-        ticker = m.get_ticker("XVG", "XMR")
-        self.assertIsNone(ticker)
-
-        self.ccxt.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"),
-            ])
+        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.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
-        m1b = market.Market(self.ccxt)
-        m1b.get_ticker("ETH", "ETC")
-        self.ccxt.fetch_ticker.assert_not_called()
-
-        self.ccxt = mock.Mock(spec=market.ccxt.poloniex)
-        m2 = market.Market(self.ccxt)
-        self.ccxt.fetch_ticker.side_effect = [
-                { "bid": 1, "ask": 3 },
-                { "bid": 1.2, "ask": 3.5 },
-                ]
-        ticker1 = m2.get_ticker("ETH", "ETC")
-        ticker2 = m2.get_ticker("ETH", "ETC")
-        ticker3 = m2.get_ticker("ETC", "ETH")
-        self.ccxt.fetch_ticker.assert_called_once_with("ETH/ETC")
-        self.assertEqual(1, ticker1["bid"])
-        self.assertDictEqual(ticker1, ticker2)
-        self.assertDictEqual(ticker1, ticker3["original"])
-
-        ticker4 = m2.get_ticker("ETH", "ETC", refresh=True)
-        ticker5 = m2.get_ticker("ETH", "ETC")
-        self.assertEqual(1.2, ticker4["bid"])
-        self.assertDictEqual(ticker4, ticker5)
-
-        self.ccxt = mock.Mock(spec=market.ccxt.binance)
-        m3 = market.Market(self.ccxt)
-        self.ccxt.fetch_ticker.side_effect = [
-                { "bid": 1, "ask": 3 },
-                { "bid": 1.2, "ask": 3.5 },
-                ]
-        ticker6 = m3.get_ticker("ETH", "ETC")
-        m3.ticker_cache_timestamp -= 4
-        ticker7 = m3.get_ticker("ETH", "ETC")
-        m3.ticker_cache_timestamp -= 2
-        ticker8 = m3.get_ticker("ETH", "ETC")
-        self.assertDictEqual(ticker6, ticker7)
-        self.assertEqual(1.2, ticker8["bid"])
+        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"])
+
+            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"])
+
+            ticker = m.get_ticker("XVG", "XMR")
+            self.assertIsNone(ticker)
 
     def test_fetch_fees(self):
         m = market.Market(self.ccxt)
@@ -906,9 +895,9 @@ class MarketTest(WebMockTestCase):
                 trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
 
                 m.trades.all = [trade1, trade2, trade3]
-                balance1 = portfolio.Balance("BTC", { "margin_free": "0" })
-                balance2 = portfolio.Balance("USDT", { "margin_free": "100" })
-                balance3 = portfolio.Balance("ETC", { "margin_free": "10" })
+                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()
@@ -921,8 +910,8 @@ class MarketTest(WebMockTestCase):
                     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", 50, "margin", "exchange")
-                    self.ccxt.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange")
+                    self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
+                    self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
   
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class TradeStoreTest(WebMockTestCase):
@@ -1001,16 +990,24 @@ class TradeStoreTest(WebMockTestCase):
 
         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.is_fullfiled = False
+        trade_mock2.is_fullfiled = False
+        trade_mock3.is_fullfiled = True
 
         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()
@@ -1108,6 +1105,21 @@ class TradeStoreTest(WebMockTestCase):
             order_mock2.get_status.assert_called()
             order_mock3.get_status.assert_called()
 
+    def test_pending(self):
+        trade_mock1 = mock.Mock()
+        trade_mock1.is_fullfiled = False
+        trade_mock2 = mock.Mock()
+        trade_mock2.is_fullfiled = False
+        trade_mock3 = mock.Mock()
+        trade_mock3.is_fullfiled = True
+
+        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):
@@ -1301,13 +1313,16 @@ class TradeTest(WebMockTestCase):
         self.assertEqual(self.m, trade.market)
 
         with self.assertRaises(AssertionError):
-            portfolio.Trade(value_from, value_to, "ETC", self.m)
+            portfolio.Trade(value_from, -value_to, "ETH", self.m)
         with self.assertRaises(AssertionError):
-            value_from.linked_to = None
-            portfolio.Trade(value_from, value_to, "ETH", self.m)
+            portfolio.Trade(value_from, value_to, "ETC", self.m)
         with self.assertRaises(AssertionError):
             value_from.currency = "ETH"
             portfolio.Trade(value_from, value_to, "ETH", self.m)
+            value_from.currency = "BTC"
+        with self.assertRaises(AssertionError):
+            value_from2 = portfolio.Amount("BTC", "1.0")
+            portfolio.Trade(value_from2, value_to, "ETH", self.m)
 
         value_from = portfolio.Amount("BTC", 0)
         trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
@@ -1374,6 +1389,28 @@ class TradeTest(WebMockTestCase):
 
         self.assertEqual("short", trade.trade_type)
 
+    def test_is_fullfiled(self):
+        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", self.m)
+
+        order1 = mock.Mock()
+        order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
+
+        order2 = mock.Mock()
+        order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
+        trade.orders.append(order1)
+        trade.orders.append(order2)
+
+        self.assertFalse(trade.is_fullfiled)
+
+        order3 = mock.Mock()
+        order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
+        trade.orders.append(order3)
+
+        self.assertTrue(trade.is_fullfiled)
+
     def test_filled_amount(self):
         value_from = portfolio.Amount("BTC", "0.5")
         value_from.linked_to = portfolio.Amount("ETH", "10.0")
@@ -1588,7 +1625,7 @@ class TradeTest(WebMockTestCase):
         self.assertEqual(2, self.m.report.log_order.call_count)
         calls = [
                 mock.call(order_mock, 2, update="adjusting",
-                    compute_value='lambda x, y: (x[y] + x["average"]) / 2',
+                    compute_value=mock.ANY,
                     new_order=new_order_mock),
                 mock.call(order_mock, 2, new_order=new_order_mock),
                 ]
@@ -1607,7 +1644,7 @@ class TradeTest(WebMockTestCase):
         self.m.report.log_order.assert_called()
         calls = [
                 mock.call(order_mock, 5, update="adjusting",
-                    compute_value='lambda x, y: (x[y]*2 + x["average"]) / 3',
+                    compute_value=mock.ANY,
                     new_order=new_order_mock),
                 mock.call(order_mock, 5, new_order=new_order_mock),
                 ]
@@ -1831,7 +1868,7 @@ class OrderTest(WebMockTestCase):
 
         order.cancel()
         self.m.ccxt.cancel_order.assert_called_with(42)
-        fetch.assert_called_once_with(force=True)
+        fetch.assert_called_once_with()
         self.m.report.log_debug_action.assert_not_called()
 
     def test_dust_amount_remaining(self):
@@ -1850,11 +1887,9 @@ class OrderTest(WebMockTestCase):
                 D("0.1"), "BTC", "long", self.m, "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):
@@ -1957,61 +1992,38 @@ class OrderTest(WebMockTestCase):
 
     @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:
-            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
-                    D("0.1"), "BTC", "long", self.m, "trade")
-            order.id = 45
-            with self.subTest(debug=True):
-                self.m.debug = True
-                order.fetch()
-                time_mock.assert_not_called()
-                self.m.report.log_debug_action.assert_called_once()
-                self.m.report.log_debug_action.reset_mock()
-                order.fetch(force=True)
-                time_mock.assert_not_called()
-                self.m.ccxt.fetch_order.assert_not_called()
-                fetch_mouvements.assert_not_called()
-                self.m.report.log_debug_action.assert_called_once()
-                self.m.report.log_debug_action.reset_mock()
-            self.assertIsNone(order.fetch_cache_timestamp)
-
-            with self.subTest(debug=False):
-                self.m.debug = False
-                time_mock.return_value = time
-                self.m.ccxt.fetch_order.return_value = {
-                        "status": "foo",
-                        "datetime": "timestamp"
-                        }
-                order.fetch()
-
-                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
-                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))
-
-                self.m.ccxt.fetch_order.reset_mock()
-                fetch_mouvements.reset_mock()
-
-                time_mock.return_value = time + 8
-                order.fetch()
-                self.m.ccxt.fetch_order.assert_not_called()
-                fetch_mouvements.assert_not_called()
+        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                D("0.1"), "BTC", "long", self.m, "trade")
+        order.id = 45
+        with self.subTest(debug=True):
+            self.m.debug = True
+            order.fetch()
+            self.m.report.log_debug_action.assert_called_once()
+            self.m.report.log_debug_action.reset_mock()
+            self.m.ccxt.fetch_order.assert_not_called()
+            fetch_mouvements.assert_not_called()
 
-                order.fetch(force=True)
-                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
-                fetch_mouvements.assert_called_once()
+        with self.subTest(debug=False):
+            self.m.debug = False
+            self.m.ccxt.fetch_order.return_value = {
+                    "status": "foo",
+                    "datetime": "timestamp"
+                    }
+            order.fetch()
 
-                self.m.ccxt.fetch_order.reset_mock()
-                fetch_mouvements.reset_mock()
+            self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
+            fetch_mouvements.assert_called_once()
+            self.assertEqual("foo", order.status)
+            self.assertEqual("timestamp", order.timestamp)
+            self.assertEqual(1, len(order.results))
+            self.m.report.log_debug_action.assert_not_called()
 
-                time_mock.return_value = time + 19
+            with self.subTest(missing_order=True):
+                self.m.ccxt.fetch_order.side_effect = [
+                        portfolio.OrderNotCached,
+                        ]
                 order.fetch()
-                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
-                fetch_mouvements.assert_called_once()
-                self.m.report.log_debug_action.assert_not_called()
+                self.assertEqual("closed_unknown", order.status)
 
     @mock.patch.object(portfolio.Order, "fetch")
     @mock.patch.object(portfolio.Order, "mark_finished_order")
@@ -2315,6 +2327,25 @@ class ReportStoreTest(WebMockTestCase):
             'total': D('10.3')
             })
 
+        add_log.reset_mock()
+        compute_value = lambda x: x["bid"]
+        report_store.log_tickers(amounts, "BTC", compute_value, "total")
+        add_log.assert_called_once_with({
+            'type': 'tickers',
+            'compute_value': 'compute_value = lambda x: x["bid"]',
+            'balance_type': 'total',
+            'currency': 'BTC',
+            'balances': {
+                'BTC': D('10'),
+                'ETH': D('0.3')
+                },
+            'rates': {
+                'BTC': None,
+                'ETH': D('0.1')
+                },
+            'total': D('10.3')
+            })
+
     @mock.patch.object(market.ReportStore, "print_log")
     @mock.patch.object(market.ReportStore, "add_log")
     def test_log_dispatch(self, add_log, print_log):
@@ -2393,6 +2424,20 @@ class ReportStoreTest(WebMockTestCase):
             'orders': ['order1', 'order2']
             })
 
+        add_log.reset_mock()
+        def compute_value(x, y):
+            return x[y]
+        report_store.log_orders(orders, tick="tick",
+                only="only", compute_value=compute_value)
+        add_log.assert_called_with({
+            'type': 'orders',
+            'only': 'only',
+            'compute_value': 'def compute_value(x, y):\n            return x[y]',
+            'tick': 'tick',
+            'orders': ['order1', 'order2']
+            })
+
+
     @mock.patch.object(market.ReportStore, "print_log")
     @mock.patch.object(market.ReportStore, "add_log")
     def test_log_order(self, add_log, print_log):
@@ -2436,16 +2481,17 @@ class ReportStoreTest(WebMockTestCase):
         add_log.reset_mock()
         print_log.reset_mock()
         with self.subTest(update="adjusting"):
+            compute_value = lambda x: (x["bid"] + x["ask"]*2)/3
             report_store.log_order(order_mock, 3,
                     update="adjusting", new_order=new_order_mock,
-                    compute_value="default")
+                    compute_value=compute_value)
             print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
             add_log.assert_called_once_with({
                 'type': 'order',
                 'tick': 3,
                 'update': 'adjusting',
                 'order': 'order',
-                'compute_value': "default",
+                'compute_value': 'compute_value = lambda x: (x["bid"] + x["ask"]*2)/3',
                 'new_order': 'new_order'
                 })