]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/commitdiff
Handle invalid nonces
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 25 Mar 2018 21:57:39 +0000 (23:57 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 25 Mar 2018 21:57:39 +0000 (23:57 +0200)
ccxt_wrapper.py
market.py
portfolio.py
test.py

index 4ed37d9376e53f9935c21db0c40944eb9daef95b..97f359fa568c7fd813784dad922e431849dfb068 100644 (file)
@@ -33,7 +33,7 @@ class poloniexE(poloniex):
         retriable = any(re.match(call, path) for call in self.RETRIABLE_CALLS)
         if api == "public" or method == "GET" or retriable:
             return retry_call(origin_request, fargs=[path], fkwargs=kwargs,
-                    tries=10, delay=1, exceptions=(RequestTimeout,))
+                    tries=10, delay=1, exceptions=(RequestTimeout, InvalidNonce))
         else:
             return origin_request(path, **kwargs)
 
index ca65bca7a0196211caedfa515e55b169ee71231d..d0e6ab4e91f7ab4aaf654fda3cfa90497bbe3991 100644 (file)
--- a/market.py
+++ b/market.py
@@ -1,4 +1,4 @@
-from ccxt import ExchangeError, NotSupported, RequestTimeout
+from ccxt import ExchangeError, NotSupported, RequestTimeout, InvalidNonce
 import ccxt_wrapper as ccxt
 import time
 import psycopg2
@@ -89,7 +89,7 @@ class Market:
         finally:
             self.store_report()
 
-    @retry(RequestTimeout, tries=5)
+    @retry((RequestTimeout, InvalidNonce), tries=5)
     def move_balances(self):
         needed_in_margin = {} 
         moving_to_margin = {}
@@ -114,7 +114,7 @@ class Market:
                     self.ccxt.transfer_balance(currency, delta, "exchange", "margin")
                 elif delta < 0:
                     self.ccxt.transfer_balance(currency, -delta, "margin", "exchange")
-            except RequestTimeout as e:
+            except (RequestTimeout, InvalidNonce) as e:
                 self.report.log_error(action, message="Retrying", exception=e)
                 self.report.log_move_balances(needed_in_margin, moving_to_margin)
                 self.balances.fetch_balances()
index 9c586769575cf1ef33794856a161be40274fce01..9dae23eeb09a58755376da51e12cfe81dd67cc6d 100644 (file)
@@ -1,7 +1,7 @@
 from datetime import datetime
 from retry import retry
 from decimal import Decimal as D, ROUND_DOWN
-from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound, RequestTimeout
+from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound, RequestTimeout, InvalidNonce
 
 class Computation:
     computations = {
@@ -477,7 +477,7 @@ class Order:
     def finished(self):
         return self.status.startswith("closed") or self.status == "canceled" or self.status == "error"
 
-    @retry((InsufficientFunds, RetryException))
+    @retry((InsufficientFunds, RetryException, InvalidNonce))
     def run(self):
         self.tries += 1
         symbol = "{}/{}".format(self.amount.currency, self.base_currency)
@@ -496,6 +496,14 @@ class Order:
                 self.status = "closed"
                 self.mark_finished_order()
                 return
+            except InvalidNonce as e:
+                if self.tries < 5:
+                    self.market.report.log_error(action, message="Retrying after invalid nonce", exception=e)
+                    raise e
+                else:
+                    self.market.report.log_error(action, message="Giving up {} after invalid nonce".format(self), exception=e)
+                    self.status = "error"
+                    return
             except RequestTimeout as e:
                 if not self.retrieve_order():
                     if self.tries < 5:
diff --git a/test.py b/test.py
index 5b9e2b4244cf7dcca5c8d917c7856ded88ab75c3..c229801246219870d99e5f8c574793dd96f1ddcc 100644 (file)
--- a/test.py
+++ b/test.py
@@ -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()
@@ -1461,16 +1461,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()
@@ -3213,6 +3215,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"),