diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-04 23:35:16 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-04 23:35:16 +0100 |
commit | f9226903cb53a9b303a26de562e321159349f8df (patch) | |
tree | 671e139c9cadaf4070287f50d1f71c0d26d6ba80 | |
parent | c5a7f2863f5c041ff906b2407a74971e2178a729 (diff) | |
download | Trader-f9226903cb53a9b303a26de562e321159349f8df.tar.gz Trader-f9226903cb53a9b303a26de562e321159349f8df.tar.zst Trader-f9226903cb53a9b303a26de562e321159349f8df.zip |
Fixes after night runv0.5
- Currency pair doesn’t work when fetching orders
- Store error in main to report.
- Cancel orders when closing trade
- Print closed status of trade to repr
- Don’t try to cancel not cancellable orders
-rw-r--r-- | main.py | 5 | ||||
-rw-r--r-- | portfolio.py | 26 | ||||
-rw-r--r-- | test.py | 105 |
3 files changed, 102 insertions, 34 deletions
@@ -10,6 +10,9 @@ for market_config, user_id in helper.main_fetch_markets(pg_config, args.user): | |||
10 | user_market = market.Market.from_config(market_config, debug=args.debug) | 10 | user_market = market.Market.from_config(market_config, debug=args.debug) |
11 | helper.main_process_market(user_market, args.action, before=args.before, after=args.after) | 11 | helper.main_process_market(user_market, args.action, before=args.before, after=args.after) |
12 | except Exception as e: | 12 | except Exception as e: |
13 | print("{}: {}".format(e.__class__.__name__, e)) | 13 | try: |
14 | user_market.report.log_error("main", exception=e) | ||
15 | except: | ||
16 | print("{}: {}".format(e.__class__.__name__, e)) | ||
14 | finally: | 17 | finally: |
15 | helper.main_store_report(report_path, user_id, user_market) | 18 | helper.main_store_report(report_path, user_id, user_market) |
diff --git a/portfolio.py b/portfolio.py index f27e84f..0f2c011 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -3,7 +3,7 @@ from datetime import datetime, timedelta | |||
3 | from decimal import Decimal as D, ROUND_DOWN | 3 | from decimal import Decimal as D, ROUND_DOWN |
4 | from json import JSONDecodeError | 4 | from json import JSONDecodeError |
5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError | 5 | from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError |
6 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached | 6 | from ccxt import ExchangeError, InsufficientFunds, ExchangeNotAvailable, InvalidOrder, OrderNotCached, OrderNotFound |
7 | from retry import retry | 7 | from retry import retry |
8 | import requests | 8 | import requests |
9 | 9 | ||
@@ -333,6 +333,8 @@ class Trade: | |||
333 | return not (self.is_fullfiled or self.closed) | 333 | return not (self.is_fullfiled or self.closed) |
334 | 334 | ||
335 | def close(self): | 335 | def close(self): |
336 | for order in self.orders: | ||
337 | order.cancel() | ||
336 | self.closed = True | 338 | self.closed = True |
337 | 339 | ||
338 | @property | 340 | @property |
@@ -467,11 +469,19 @@ class Trade: | |||
467 | } | 469 | } |
468 | 470 | ||
469 | def __repr__(self): | 471 | def __repr__(self): |
470 | return "Trade({} -> {} in {}, {})".format( | 472 | if self.closed and not self.is_fullfiled: |
473 | closed = " ❌" | ||
474 | elif self.is_fullfiled: | ||
475 | closed = " ✔" | ||
476 | else: | ||
477 | closed = "" | ||
478 | |||
479 | return "Trade({} -> {} in {}, {}{})".format( | ||
471 | self.value_from, | 480 | self.value_from, |
472 | self.value_to, | 481 | self.value_to, |
473 | self.currency, | 482 | self.currency, |
474 | self.action) | 483 | self.action, |
484 | closed) | ||
475 | 485 | ||
476 | def print_with_order(self, ind=""): | 486 | def print_with_order(self, ind=""): |
477 | self.market.report.print_log("{}{}".format(ind, self)) | 487 | self.market.report.print_log("{}{}".format(ind, self)) |
@@ -600,7 +610,7 @@ class Order: | |||
600 | self.market.report.log_debug_action("Fetching {}".format(self)) | 610 | self.market.report.log_debug_action("Fetching {}".format(self)) |
601 | return | 611 | return |
602 | try: | 612 | try: |
603 | result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency) | 613 | result = self.market.ccxt.fetch_order(self.id) |
604 | self.results.append(result) | 614 | self.results.append(result) |
605 | self.status = result["status"] | 615 | self.status = result["status"] |
606 | # Time at which the order started | 616 | # Time at which the order started |
@@ -645,8 +655,12 @@ class Order: | |||
645 | self.market.report.log_debug_action("Mark {} as cancelled".format(self)) | 655 | self.market.report.log_debug_action("Mark {} as cancelled".format(self)) |
646 | self.status = "canceled" | 656 | self.status = "canceled" |
647 | return | 657 | return |
648 | self.market.ccxt.cancel_order(self.id) | 658 | if self.open and self.id is not None: |
649 | self.fetch() | 659 | try: |
660 | self.market.ccxt.cancel_order(self.id) | ||
661 | except OrderNotFound as e: # Closed inbetween | ||
662 | self.market.report.log_error("cancel_order", message="Already cancelled order", exception=e) | ||
663 | self.fetch() | ||
650 | 664 | ||
651 | class Mouvement: | 665 | class Mouvement: |
652 | def __init__(self, currency, base_currency, hash_): | 666 | def __init__(self, currency, base_currency, hash_): |
@@ -1746,25 +1746,46 @@ class TradeTest(WebMockTestCase): | |||
1746 | trade.orders.append(order_mock1) | 1746 | trade.orders.append(order_mock1) |
1747 | trade.orders.append(order_mock2) | 1747 | trade.orders.append(order_mock2) |
1748 | 1748 | ||
1749 | trade.print_with_order() | 1749 | with mock.patch.object(trade, "filled_amount") as filled: |
1750 | filled.return_value = portfolio.Amount("BTC", "0.1") | ||
1750 | 1751 | ||
1751 | self.m.report.print_log.assert_called() | 1752 | trade.print_with_order() |
1752 | calls = self.m.report.print_log.mock_calls | 1753 | |
1753 | self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0])) | 1754 | self.m.report.print_log.assert_called() |
1754 | self.assertEqual("\tMock 1", str(calls[1][1][0])) | 1755 | calls = self.m.report.print_log.mock_calls |
1755 | self.assertEqual("\tMock 2", str(calls[2][1][0])) | 1756 | self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0])) |
1756 | self.assertEqual("\t\tMouvement 1", str(calls[3][1][0])) | 1757 | self.assertEqual("\tMock 1", str(calls[1][1][0])) |
1757 | self.assertEqual("\t\tMouvement 2", str(calls[4][1][0])) | 1758 | self.assertEqual("\tMock 2", str(calls[2][1][0])) |
1759 | self.assertEqual("\t\tMouvement 1", str(calls[3][1][0])) | ||
1760 | self.assertEqual("\t\tMouvement 2", str(calls[4][1][0])) | ||
1761 | |||
1762 | self.m.report.print_log.reset_mock() | ||
1763 | |||
1764 | filled.return_value = portfolio.Amount("BTC", "0.5") | ||
1765 | trade.print_with_order() | ||
1766 | calls = self.m.report.print_log.mock_calls | ||
1767 | self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls[0][1][0])) | ||
1768 | |||
1769 | self.m.report.print_log.reset_mock() | ||
1770 | |||
1771 | filled.return_value = portfolio.Amount("BTC", "0.1") | ||
1772 | trade.closed = True | ||
1773 | trade.print_with_order() | ||
1774 | calls = self.m.report.print_log.mock_calls | ||
1775 | self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls[0][1][0])) | ||
1758 | 1776 | ||
1759 | def test_close(self): | 1777 | def test_close(self): |
1760 | value_from = portfolio.Amount("BTC", "0.5") | 1778 | value_from = portfolio.Amount("BTC", "0.5") |
1761 | value_from.linked_to = portfolio.Amount("ETH", "10.0") | 1779 | value_from.linked_to = portfolio.Amount("ETH", "10.0") |
1762 | value_to = portfolio.Amount("BTC", "1.0") | 1780 | value_to = portfolio.Amount("BTC", "1.0") |
1763 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 1781 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) |
1782 | order1 = mock.Mock() | ||
1783 | trade.orders.append(order1) | ||
1764 | 1784 | ||
1765 | trade.close() | 1785 | trade.close() |
1766 | 1786 | ||
1767 | self.assertEqual(True, trade.closed) | 1787 | self.assertEqual(True, trade.closed) |
1788 | order1.cancel.assert_called_once_with() | ||
1768 | 1789 | ||
1769 | def test_pending(self): | 1790 | def test_pending(self): |
1770 | value_from = portfolio.Amount("BTC", "0.5") | 1791 | value_from = portfolio.Amount("BTC", "0.5") |
@@ -1888,27 +1909,57 @@ class OrderTest(WebMockTestCase): | |||
1888 | 1909 | ||
1889 | @mock.patch.object(portfolio.Order, "fetch") | 1910 | @mock.patch.object(portfolio.Order, "fetch") |
1890 | def test_cancel(self, fetch): | 1911 | def test_cancel(self, fetch): |
1891 | self.m.debug = True | 1912 | with self.subTest(debug=True): |
1892 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 1913 | self.m.debug = True |
1893 | D("0.1"), "BTC", "long", self.m, "trade") | 1914 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
1894 | order.status = "open" | 1915 | D("0.1"), "BTC", "long", self.m, "trade") |
1916 | order.status = "open" | ||
1895 | 1917 | ||
1896 | order.cancel() | 1918 | order.cancel() |
1897 | self.m.ccxt.cancel_order.assert_not_called() | 1919 | self.m.ccxt.cancel_order.assert_not_called() |
1898 | self.m.report.log_debug_action.assert_called_once() | 1920 | self.m.report.log_debug_action.assert_called_once() |
1899 | self.m.report.log_debug_action.reset_mock() | 1921 | self.m.report.log_debug_action.reset_mock() |
1900 | self.assertEqual("canceled", order.status) | 1922 | self.assertEqual("canceled", order.status) |
1901 | 1923 | ||
1902 | self.m.debug = False | 1924 | with self.subTest(desc="Nominal case"): |
1903 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 1925 | self.m.debug = False |
1904 | D("0.1"), "BTC", "long", self.m, "trade") | 1926 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
1905 | order.status = "open" | 1927 | D("0.1"), "BTC", "long", self.m, "trade") |
1906 | order.id = 42 | 1928 | order.status = "open" |
1929 | order.id = 42 | ||
1907 | 1930 | ||
1908 | order.cancel() | 1931 | order.cancel() |
1909 | self.m.ccxt.cancel_order.assert_called_with(42) | 1932 | self.m.ccxt.cancel_order.assert_called_with(42) |
1910 | fetch.assert_called_once_with() | 1933 | fetch.assert_called_once_with() |
1911 | self.m.report.log_debug_action.assert_not_called() | 1934 | self.m.report.log_debug_action.assert_not_called() |
1935 | |||
1936 | with self.subTest(exception=True): | ||
1937 | self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound | ||
1938 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | ||
1939 | D("0.1"), "BTC", "long", self.m, "trade") | ||
1940 | order.status = "open" | ||
1941 | order.id = 42 | ||
1942 | order.cancel() | ||
1943 | self.m.ccxt.cancel_order.assert_called_with(42) | ||
1944 | self.m.report.log_error.assert_called_once() | ||
1945 | |||
1946 | self.m.reset_mock() | ||
1947 | with self.subTest(id=None): | ||
1948 | self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound | ||
1949 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | ||
1950 | D("0.1"), "BTC", "long", self.m, "trade") | ||
1951 | order.status = "open" | ||
1952 | order.cancel() | ||
1953 | self.m.ccxt.cancel_order.assert_not_called() | ||
1954 | |||
1955 | self.m.reset_mock() | ||
1956 | with self.subTest(open=False): | ||
1957 | self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound | ||
1958 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | ||
1959 | D("0.1"), "BTC", "long", self.m, "trade") | ||
1960 | order.status = "closed" | ||
1961 | order.cancel() | ||
1962 | self.m.ccxt.cancel_order.assert_not_called() | ||
1912 | 1963 | ||
1913 | def test_dust_amount_remaining(self): | 1964 | def test_dust_amount_remaining(self): |
1914 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 1965 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
@@ -2050,7 +2101,7 @@ class OrderTest(WebMockTestCase): | |||
2050 | } | 2101 | } |
2051 | order.fetch() | 2102 | order.fetch() |
2052 | 2103 | ||
2053 | self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") | 2104 | self.m.ccxt.fetch_order.assert_called_once_with(45) |
2054 | fetch_mouvements.assert_called_once() | 2105 | fetch_mouvements.assert_called_once() |
2055 | self.assertEqual("foo", order.status) | 2106 | self.assertEqual("foo", order.status) |
2056 | self.assertEqual("timestamp", order.timestamp) | 2107 | self.assertEqual("timestamp", order.timestamp) |