aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-25 19:08:48 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-25 20:28:33 +0200
commit337c8286cc31d81ffdad06a225996f86c46c46f0 (patch)
tree37bd365a49fe31d2c6424e37edcba729bc9cb7b0
parent45fffd4963005a1f3957868f9ddb1aa7ec66c0e3 (diff)
downloadTrader-337c8286cc31d81ffdad06a225996f86c46c46f0.tar.gz
Trader-337c8286cc31d81ffdad06a225996f86c46c46f0.tar.zst
Trader-337c8286cc31d81ffdad06a225996f86c46c46f0.zip
Handle timeouts for move_balances
-rw-r--r--market.py22
-rw-r--r--test.py133
2 files changed, 149 insertions, 6 deletions
diff --git a/market.py b/market.py
index 055967c..ca65bca 100644
--- a/market.py
+++ b/market.py
@@ -1,10 +1,11 @@
1from ccxt import ExchangeError, NotSupported 1from ccxt import ExchangeError, NotSupported, RequestTimeout
2import ccxt_wrapper as ccxt 2import ccxt_wrapper as ccxt
3import time 3import time
4import psycopg2 4import psycopg2
5from store import * 5from store import *
6from cachetools.func import ttl_cache 6from cachetools.func import ttl_cache
7from datetime import datetime 7from datetime import datetime
8from retry import retry
8import portfolio 9import portfolio
9 10
10class Market: 11class Market:
@@ -88,6 +89,7 @@ class Market:
88 finally: 89 finally:
89 self.store_report() 90 self.store_report()
90 91
92 @retry(RequestTimeout, tries=5)
91 def move_balances(self): 93 def move_balances(self):
92 needed_in_margin = {} 94 needed_in_margin = {}
93 moving_to_margin = {} 95 moving_to_margin = {}
@@ -102,13 +104,21 @@ class Market:
102 current_balance = self.balances.all[currency].margin_available 104 current_balance = self.balances.all[currency].margin_available
103 moving_to_margin[currency] = (needed - current_balance) 105 moving_to_margin[currency] = (needed - current_balance)
104 delta = moving_to_margin[currency].value 106 delta = moving_to_margin[currency].value
107 action = "Moving {} from exchange to margin".format(moving_to_margin[currency])
108
105 if self.debug and delta != 0: 109 if self.debug and delta != 0:
106 self.report.log_debug_action("Moving {} from exchange to margin".format(moving_to_margin[currency])) 110 self.report.log_debug_action(action)
107 continue 111 continue
108 if delta > 0: 112 try:
109 self.ccxt.transfer_balance(currency, delta, "exchange", "margin") 113 if delta > 0:
110 elif delta < 0: 114 self.ccxt.transfer_balance(currency, delta, "exchange", "margin")
111 self.ccxt.transfer_balance(currency, -delta, "margin", "exchange") 115 elif delta < 0:
116 self.ccxt.transfer_balance(currency, -delta, "margin", "exchange")
117 except RequestTimeout as e:
118 self.report.log_error(action, message="Retrying", exception=e)
119 self.report.log_move_balances(needed_in_margin, moving_to_margin)
120 self.balances.fetch_balances()
121 raise e
112 self.report.log_move_balances(needed_in_margin, moving_to_margin) 122 self.report.log_move_balances(needed_in_margin, moving_to_margin)
113 123
114 self.balances.fetch_balances() 124 self.balances.fetch_balances()
diff --git a/test.py b/test.py
index 18616c1..ea1fd9a 100644
--- a/test.py
+++ b/test.py
@@ -1444,6 +1444,139 @@ class MarketTest(WebMockTestCase):
1444 self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin") 1444 self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
1445 self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange") 1445 self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
1446 1446
1447 m.report.reset_mock()
1448 fetch_balances.reset_mock()
1449 with self.subTest(retry=True):
1450 with mock.patch("market.ReportStore"):
1451 m = market.Market(self.ccxt, self.market_args())
1452
1453 value_from = portfolio.Amount("BTC", "0.0")
1454 value_from.linked_to = portfolio.Amount("ETH", "0.0")
1455 value_to = portfolio.Amount("BTC", "-3.0")
1456 trade = portfolio.Trade(value_from, value_to, "ETH", m)
1457
1458 m.trades.all = [trade]
1459 balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
1460 m.balances.all = {"BTC": balance}
1461
1462 m.ccxt.transfer_balance.side_effect = [
1463 market.ccxt.RequestTimeout,
1464 True
1465 ]
1466 m.move_balances()
1467 self.ccxt.transfer_balance.assert_has_calls([
1468 mock.call("BTC", 3, "exchange", "margin"),
1469 mock.call("BTC", 3, "exchange", "margin")
1470 ])
1471 self.assertEqual(2, fetch_balances.call_count)
1472 m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
1473 self.assertEqual(2, m.report.log_move_balances.call_count)
1474
1475 self.ccxt.transfer_balance.reset_mock()
1476 m.report.reset_mock()
1477 fetch_balances.reset_mock()
1478 with self.subTest(retry=True, too_much=True):
1479 with mock.patch("market.ReportStore"):
1480 m = market.Market(self.ccxt, self.market_args())
1481
1482 value_from = portfolio.Amount("BTC", "0.0")
1483 value_from.linked_to = portfolio.Amount("ETH", "0.0")
1484 value_to = portfolio.Amount("BTC", "-3.0")
1485 trade = portfolio.Trade(value_from, value_to, "ETH", m)
1486
1487 m.trades.all = [trade]
1488 balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
1489 m.balances.all = {"BTC": balance}
1490
1491 m.ccxt.transfer_balance.side_effect = [
1492 market.ccxt.RequestTimeout,
1493 market.ccxt.RequestTimeout,
1494 market.ccxt.RequestTimeout,
1495 market.ccxt.RequestTimeout,
1496 market.ccxt.RequestTimeout,
1497 ]
1498 with self.assertRaises(market.ccxt.RequestTimeout):
1499 m.move_balances()
1500
1501 self.ccxt.transfer_balance.reset_mock()
1502 m.report.reset_mock()
1503 fetch_balances.reset_mock()
1504 with self.subTest(retry=True, partial_result=True):
1505 with mock.patch("market.ReportStore"):
1506 m = market.Market(self.ccxt, self.market_args())
1507
1508 value_from = portfolio.Amount("BTC", "1.0")
1509 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1510 value_to = portfolio.Amount("BTC", "10.0")
1511 trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
1512
1513 value_from = portfolio.Amount("BTC", "0.0")
1514 value_from.linked_to = portfolio.Amount("ETH", "0.0")
1515 value_to = portfolio.Amount("BTC", "-3.0")
1516 trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
1517
1518 value_from = portfolio.Amount("USDT", "0.0")
1519 value_from.linked_to = portfolio.Amount("XVG", "0.0")
1520 value_to = portfolio.Amount("USDT", "-50.0")
1521 trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
1522
1523 m.trades.all = [trade1, trade2, trade3]
1524 balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
1525 balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
1526 balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
1527 m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
1528
1529 call_counts = { "BTC": 0, "USDT": 0, "ETC": 0 }
1530 def _transfer_balance(currency, amount, from_, to_):
1531 call_counts[currency] += 1
1532 if currency == "BTC":
1533 m.balances.all["BTC"] = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" })
1534 if currency == "USDT":
1535 if call_counts["USDT"] == 1:
1536 raise market.ccxt.RequestTimeout
1537 else:
1538 m.balances.all["USDT"] = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" })
1539 if currency == "ETC":
1540 m.balances.all["ETC"] = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" })
1541
1542
1543 m.ccxt.transfer_balance.side_effect = _transfer_balance
1544
1545 m.move_balances()
1546 self.ccxt.transfer_balance.assert_has_calls([
1547 mock.call("BTC", 3, "exchange", "margin"),
1548 mock.call('USDT', 100, 'exchange', 'margin'),
1549 mock.call('USDT', 100, 'exchange', 'margin'),
1550 mock.call("ETC", 5, "margin", "exchange")
1551 ])
1552 self.assertEqual(2, fetch_balances.call_count)
1553 m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
1554 self.assertEqual(2, m.report.log_move_balances.call_count)
1555 m.report.log_move_balances.asser_has_calls([
1556 mock.call(
1557 {
1558 'BTC': portfolio.Amount("BTC", "3"),
1559 'USDT': portfolio.Amount("USDT", "150"),
1560 'ETC': portfolio.Amount("ETC", "10"),
1561 },
1562 {
1563 'BTC': portfolio.Amount("BTC", "3"),
1564 'USDT': portfolio.Amount("USDT", "100"),
1565 }),
1566 mock.call(
1567 {
1568 'BTC': portfolio.Amount("BTC", "3"),
1569 'USDT': portfolio.Amount("USDT", "150"),
1570 'ETC': portfolio.Amount("ETC", "10"),
1571 },
1572 {
1573 'BTC': portfolio.Amount("BTC", "0"),
1574 'USDT': portfolio.Amount("USDT", "100"),
1575 'ETC': portfolio.Amount("ETC", "-5"),
1576 }),
1577 ])
1578
1579
1447 def test_store_file_report(self): 1580 def test_store_file_report(self):
1448 file_open = mock.mock_open() 1581 file_open = mock.mock_open()
1449 m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1) 1582 m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1)