]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - test.py
Add logging at market instance creation
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
diff --git a/test.py b/test.py
index 5b9e2b4244cf7dcca5c8d917c7856ded88ab75c3..29403d46c6e48d53b15d61c4e5f9433594ccd329 100644 (file)
--- a/test.py
+++ b/test.py
@@ -27,7 +27,7 @@ class WebMockTestCase(unittest.TestCase):
         return type('Args', (object,), { "debug": debug, "quiet": quiet })()
 
     def setUp(self):
-        super(WebMockTestCase, self).setUp()
+        super().setUp()
         self.wm = requests_mock.Mocker()
         self.wm.start()
 
@@ -55,12 +55,12 @@ class WebMockTestCase(unittest.TestCase):
         for patcher in self.patchers:
             patcher.stop()
         self.wm.stop()
-        super(WebMockTestCase, self).tearDown()
+        super().tearDown()
 
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class poloniexETest(unittest.TestCase):
     def setUp(self):
-        super(poloniexETest, self).setUp()
+        super().setUp()
         self.wm = requests_mock.Mocker()
         self.wm.start()
 
@@ -68,7 +68,7 @@ class poloniexETest(unittest.TestCase):
 
     def tearDown(self):
         self.wm.stop()
-        super(poloniexETest, self).tearDown()
+        super().tearDown()
 
     def test__init(self):
         with mock.patch("market.ccxt.poloniexE.session") as session:
@@ -101,7 +101,7 @@ class poloniexETest(unittest.TestCase):
                     retry_call.assert_called_with(request,
                             delay=1, tries=10, fargs=["foo"],
                             fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
-                            exceptions=(market.ccxt.RequestTimeout,))
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                     request.assert_not_called()
 
                 with self.subTest(desc="private GET"):
@@ -109,7 +109,7 @@ class poloniexETest(unittest.TestCase):
                     retry_call.assert_called_with(request,
                             delay=1, tries=10, fargs=["foo"],
                             fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
-                            exceptions=(market.ccxt.RequestTimeout,))
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                     request.assert_not_called()
 
                 with self.subTest(desc="private POST regexp"):
@@ -117,7 +117,7 @@ class poloniexETest(unittest.TestCase):
                     retry_call.assert_called_with(request,
                             delay=1, tries=10, fargs=["returnFoo"],
                             fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
-                            exceptions=(market.ccxt.RequestTimeout,))
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                     request.assert_not_called()
 
                 with self.subTest(desc="private POST non-regexp"):
@@ -125,7 +125,7 @@ class poloniexETest(unittest.TestCase):
                     retry_call.assert_called_with(request,
                             delay=1, tries=10, fargs=["getMarginPosition"],
                             fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
-                            exceptions=(market.ccxt.RequestTimeout,))
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                     request.assert_not_called()
             retry_call.reset_mock()
             request.reset_mock()
@@ -609,7 +609,7 @@ class LockedVar(unittest.TestCase):
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class PortfolioTest(WebMockTestCase):
     def setUp(self):
-        super(PortfolioTest, self).setUp()
+        super().setUp()
 
         with open("test_samples/test_portfolio.json") as example:
             self.json_response = example.read()
@@ -1154,7 +1154,7 @@ class BalanceTest(WebMockTestCase):
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class MarketTest(WebMockTestCase):
     def setUp(self):
-        super(MarketTest, self).setUp()
+        super().setUp()
 
         self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
 
@@ -1181,9 +1181,12 @@ class MarketTest(WebMockTestCase):
             with self.subTest(quiet=False):
                 m = market.Market(self.ccxt, self.market_args(quiet=False))
                 report_store.assert_called_with(m, verbose_print=True)
+                report_store().log_market.assert_called_once()
+            report_store.reset_mock()
             with self.subTest(quiet=True):
                 m = market.Market(self.ccxt, self.market_args(quiet=True))
                 report_store.assert_called_with(m, verbose_print=False)
+                report_store().log_market.assert_called_once()
 
     @mock.patch("market.ccxt")
     def test_from_config(self, ccxt):
@@ -1403,6 +1406,38 @@ class MarketTest(WebMockTestCase):
                 else:
                     time_mock.assert_called_with(sleep)
 
+        with self.subTest("disappearing order"), \
+                mock.patch("market.ReportStore"):
+            all_orders.reset_mock()
+            m = market.Market(self.ccxt, self.market_args())
+
+            order_mock1 = mock.Mock()
+            order_mock2 = mock.Mock()
+            all_orders.side_effect = [
+                    [order_mock1, order_mock2],
+                    [order_mock1, order_mock2],
+
+                    [order_mock1, order_mock2],
+                    [order_mock1, order_mock2],
+
+                    []
+                    ]
+
+            order_mock1.get_status.side_effect = ["open", "closed"]
+            order_mock2.get_status.side_effect = ["open", "error_disappeared"]
+
+            order_mock1.trade = mock.Mock()
+            trade_mock = mock.Mock()
+            order_mock2.trade = trade_mock
+
+            trade_mock.tick_actions_recreate.return_value = "tick1"
+
+            m.follow_orders()
+
+            trade_mock.tick_actions_recreate.assert_called_once_with(2)
+            trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
+            m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
+
     @mock.patch.object(market.BalanceStore, "fetch_balances")
     def test_move_balance(self, fetch_balances):
         for debug in [True, False]:
@@ -1461,16 +1496,18 @@ class MarketTest(WebMockTestCase):
 
                 m.ccxt.transfer_balance.side_effect = [
                         market.ccxt.RequestTimeout,
+                        market.ccxt.InvalidNonce,
                         True
                         ]
                 m.move_balances()
                 self.ccxt.transfer_balance.assert_has_calls([
+                    mock.call("BTC", 3, "exchange", "margin"),
                     mock.call("BTC", 3, "exchange", "margin"),
                     mock.call("BTC", 3, "exchange", "margin")
                     ])
-                self.assertEqual(2, fetch_balances.call_count)
+                self.assertEqual(3, fetch_balances.call_count)
                 m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
-                self.assertEqual(2, m.report.log_move_balances.call_count)
+                self.assertEqual(3, m.report.log_move_balances.call_count)
 
         self.ccxt.transfer_balance.reset_mock()
         m.report.reset_mock()
@@ -2054,7 +2091,7 @@ class TradeStoreTest(WebMockTestCase):
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class BalanceStoreTest(WebMockTestCase):
     def setUp(self):
-        super(BalanceStoreTest, self).setUp()
+        super().setUp()
 
         self.fetch_balance = {
                 "ETC": {
@@ -2559,6 +2596,21 @@ class TradeTest(WebMockTestCase):
                     D("125"), "FOO", "long", self.m,
                     trade, close_if_possible=False)
 
+    def test_tick_actions_recreate(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)
+
+        self.assertEqual("average", trade.tick_actions_recreate(0))
+        self.assertEqual("foo", trade.tick_actions_recreate(0, default="foo"))
+        self.assertEqual("average", trade.tick_actions_recreate(1))
+        self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(2))
+        self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(3))
+        self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(5))
+        self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(6))
+        self.assertEqual("default", trade.tick_actions_recreate(7))
+        self.assertEqual("default", trade.tick_actions_recreate(8))
 
     @mock.patch.object(portfolio.Trade, "prepare_order")
     def test_update_order(self, prepare_order):
@@ -2961,12 +3013,12 @@ class OrderTest(WebMockTestCase):
         self.m.ccxt.privatePostReturnOrderTrades.return_value = [
                 {
                     "tradeID": 42, "type": "buy", "fee": "0.0015",
-                    "date": "2017-12-30 12:00:12", "rate": "0.1",
+                    "date": "2017-12-30 13:00:12", "rate": "0.1",
                     "amount": "3", "total": "0.3"
                     },
                 {
                     "tradeID": 43, "type": "buy", "fee": "0.0015",
-                    "date": "2017-12-30 13:00:12", "rate": "0.2",
+                    "date": "2017-12-30 12:00:12", "rate": "0.2",
                     "amount": "2", "total": "0.4"
                     }
             ]
@@ -2979,8 +3031,8 @@ class OrderTest(WebMockTestCase):
 
         self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
         self.assertEqual(2, len(order.mouvements))
-        self.assertEqual(42, order.mouvements[0].id)
-        self.assertEqual(43, order.mouvements[1].id)
+        self.assertEqual(43, order.mouvements[0].id)
+        self.assertEqual(42, order.mouvements[1].id)
 
         self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
@@ -3036,8 +3088,9 @@ class OrderTest(WebMockTestCase):
         self.m.report.log_debug_action.assert_called_once()
 
     @mock.patch.object(portfolio.Order, "fetch_mouvements")
+    @mock.patch.object(portfolio.Order, "mark_disappeared_order")
     @mock.patch.object(portfolio.Order, "mark_finished_order")
-    def test_fetch(self, mark_finished_order, fetch_mouvements):
+    def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                 D("0.1"), "BTC", "long", self.m, "trade")
         order.id = 45
@@ -3048,6 +3101,7 @@ class OrderTest(WebMockTestCase):
             self.m.report.log_debug_action.reset_mock()
             self.m.ccxt.fetch_order.assert_not_called()
             mark_finished_order.assert_not_called()
+            mark_disappeared_order.assert_not_called()
             fetch_mouvements.assert_not_called()
 
         with self.subTest(debug=False):
@@ -3065,6 +3119,7 @@ class OrderTest(WebMockTestCase):
             self.assertEqual(1, len(order.results))
             self.m.report.log_debug_action.assert_not_called()
             mark_finished_order.assert_called_once()
+            mark_disappeared_order.assert_called_once()
 
             mark_finished_order.reset_mock()
             with self.subTest(missing_order=True):
@@ -3075,6 +3130,88 @@ class OrderTest(WebMockTestCase):
                 self.assertEqual("closed_unknown", order.status)
                 mark_finished_order.assert_called_once()
 
+    def test_mark_disappeared_order(self):
+        with self.subTest("Open order"):
+            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                    D("0.1"), "BTC", "long", self.m, "trade")
+            order.id = 45
+            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+                "tradeID":21336541,
+                "currencyPair":"BTC_XRP",
+                "type":"sell",
+                "rate":"0.00007013",
+                "amount":"0.00000222",
+                "total":"0.00000000",
+                "fee":"0.00150000",
+                "date":"2018-04-02 00:09:13"
+                }))
+            order.mark_disappeared_order()
+            self.assertEqual("pending", order.status)
+
+        with self.subTest("Non-zero amount"):
+            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                    D("0.1"), "BTC", "long", self.m, "trade")
+            order.id = 45
+            order.status = "closed"
+            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+                "tradeID":21336541,
+                "currencyPair":"BTC_XRP",
+                "type":"sell",
+                "rate":"0.00007013",
+                "amount":"0.00000222",
+                "total":"0.00000010",
+                "fee":"0.00150000",
+                "date":"2018-04-02 00:09:13"
+                }))
+            order.mark_disappeared_order()
+            self.assertEqual("closed", order.status)
+
+        with self.subTest("Other mouvements"):
+            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                    D("0.1"), "BTC", "long", self.m, "trade")
+            order.id = 45
+            order.status = "closed"
+            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+                "tradeID":21336541,
+                "currencyPair":"BTC_XRP",
+                "type":"sell",
+                "rate":"0.00007013",
+                "amount":"0.00000222",
+                "total":"0.00000001",
+                "fee":"0.00150000",
+                "date":"2018-04-02 00:09:13"
+                }))
+            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+                "tradeID":21336541,
+                "currencyPair":"BTC_XRP",
+                "type":"sell",
+                "rate":"0.00007013",
+                "amount":"0.00000222",
+                "total":"0.00000000",
+                "fee":"0.00150000",
+                "date":"2018-04-02 00:09:13"
+                }))
+            order.mark_disappeared_order()
+            self.assertEqual("error_disappeared", order.status)
+
+        with self.subTest("Order disappeared"):
+            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                    D("0.1"), "BTC", "long", self.m, "trade")
+            order.id = 45
+            order.status = "closed"
+            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+                "tradeID":21336541,
+                "currencyPair":"BTC_XRP",
+                "type":"sell",
+                "rate":"0.00007013",
+                "amount":"0.00000222",
+                "total":"0.00000000",
+                "fee":"0.00150000",
+                "date":"2018-04-02 00:09:13"
+                }))
+            order.mark_disappeared_order()
+            self.assertEqual("error_disappeared", order.status)
+
     @mock.patch.object(portfolio.Order, "fetch")
     def test_get_status(self, fetch):
         with self.subTest(debug=True):
@@ -3213,6 +3350,48 @@ class OrderTest(WebMockTestCase):
             self.assertEqual(5, self.m.report.log_error.call_count)
             self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception=mock.ANY)
 
+        self.m.reset_mock()
+        with self.subTest(invalid_nonce=True):
+            with self.subTest(retry_success=True):
+                order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
+                        D("0.1"), "BTC", "long", self.m, "trade")
+                self.m.ccxt.create_order.side_effect = [
+                        portfolio.InvalidNonce,
+                        portfolio.InvalidNonce,
+                        { "id": 123 },
+                        ]
+                order.run()
+                self.m.ccxt.create_order.assert_has_calls([
+                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
+                ])
+                self.assertEqual(3, self.m.ccxt.create_order.call_count)
+                self.assertEqual(3, order.tries)
+                self.m.report.log_error.assert_called()
+                self.assertEqual(2, self.m.report.log_error.call_count)
+                self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after invalid nonce", exception=mock.ANY)
+                self.assertEqual(123, order.id)
+
+            self.m.reset_mock()
+            with self.subTest(retry_success=False):
+                order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
+                        D("0.1"), "BTC", "long", self.m, "trade")
+                self.m.ccxt.create_order.side_effect = [
+                        portfolio.InvalidNonce,
+                        portfolio.InvalidNonce,
+                        portfolio.InvalidNonce,
+                        portfolio.InvalidNonce,
+                        portfolio.InvalidNonce,
+                        ]
+                order.run()
+                self.assertEqual(5, self.m.ccxt.create_order.call_count)
+                self.assertEqual(5, order.tries)
+                self.m.report.log_error.assert_called()
+                self.assertEqual(5, self.m.report.log_error.call_count)
+                self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after invalid nonce", exception=mock.ANY)
+                self.assertEqual("error", order.status)
+
         self.m.reset_mock()
         with self.subTest(request_timeout=True):
             order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
@@ -4170,6 +4349,25 @@ class ReportStoreTest(WebMockTestCase):
             'response': 'Hey'
             })
 
+    @mock.patch.object(market.ReportStore, "add_log")
+    def test_log_market(self, add_log):
+        report_store = market.ReportStore(self.m)
+        class Args:
+            def __init__(self):
+                self.debug = True
+                self.quiet = False
+
+        report_store.log_market(Args(), 4, 1, "report", True)
+        add_log.assert_called_once_with({
+            "type": "market",
+            "commit": "$Format:%H$",
+            "args": { "debug": True, "quiet": False },
+            "user_id": 4,
+            "market_id": 1,
+            "report_path": "report",
+            "debug": True
+            })
+
     @mock.patch.object(market.ReportStore, "print_log")
     @mock.patch.object(market.ReportStore, "add_log")
     def test_log_error(self, add_log, print_log):