diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-12 02:10:08 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-12 02:10:08 +0100 |
commit | 83c698c925db9dcb2d347c2a625de88d85cfeb21 (patch) | |
tree | 3d1d321c068c16551502469fbc31566620b16c6c | |
parent | 34eb08f759a440af0376727664d9422041dfbd18 (diff) | |
parent | d8deb0e9a6b7b2805e61dc19a287d5474c271cc5 (diff) | |
download | Trader-83c698c925db9dcb2d347c2a625de88d85cfeb21.tar.gz Trader-83c698c925db9dcb2d347c2a625de88d85cfeb21.tar.zst Trader-83c698c925db9dcb2d347c2a625de88d85cfeb21.zip |
Merge branch 'night_fixes' into dev
-rw-r--r-- | portfolio.py | 26 | ||||
-rw-r--r-- | test.py | 88 |
2 files changed, 76 insertions, 38 deletions
diff --git a/portfolio.py b/portfolio.py index 554b34f..69e3755 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -214,6 +214,7 @@ class Trade: | |||
214 | self.orders = [] | 214 | self.orders = [] |
215 | self.market = market | 215 | self.market = market |
216 | self.closed = False | 216 | self.closed = False |
217 | self.inverted = None | ||
217 | assert self.value_from.value * self.value_to.value >= 0 | 218 | assert self.value_from.value * self.value_to.value >= 0 |
218 | assert self.value_from.currency == self.value_to.currency | 219 | assert self.value_from.currency == self.value_to.currency |
219 | if self.value_from != 0: | 220 | if self.value_from != 0: |
@@ -238,8 +239,8 @@ class Trade: | |||
238 | else: | 239 | else: |
239 | return "dispose" | 240 | return "dispose" |
240 | 241 | ||
241 | def order_action(self, inverted): | 242 | def order_action(self): |
242 | if (self.value_from < self.value_to) != inverted: | 243 | if (self.value_from < self.value_to) != self.inverted: |
243 | return "buy" | 244 | return "buy" |
244 | else: | 245 | else: |
245 | return "sell" | 246 | return "sell" |
@@ -262,7 +263,7 @@ class Trade: | |||
262 | 263 | ||
263 | @property | 264 | @property |
264 | def is_fullfiled(self): | 265 | def is_fullfiled(self): |
265 | return abs(self.filled_amount(in_base_currency=True)) >= abs(self.delta) | 266 | return abs(self.filled_amount(in_base_currency=(not self.inverted))) >= abs(self.delta) |
266 | 267 | ||
267 | def filled_amount(self, in_base_currency=False): | 268 | def filled_amount(self, in_base_currency=False): |
268 | filled_amount = 0 | 269 | filled_amount = 0 |
@@ -308,17 +309,17 @@ class Trade: | |||
308 | if self.action is None: | 309 | if self.action is None: |
309 | return None | 310 | return None |
310 | ticker = self.market.get_ticker(self.currency, self.base_currency) | 311 | ticker = self.market.get_ticker(self.currency, self.base_currency) |
311 | inverted = ticker["inverted"] | 312 | self.inverted = ticker["inverted"] |
312 | if inverted: | 313 | if self.inverted: |
313 | ticker = ticker["original"] | 314 | ticker = ticker["original"] |
314 | rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) | 315 | rate = Computation.compute_value(ticker, self.order_action(), compute_value=compute_value) |
315 | 316 | ||
316 | # FIXME: Dust amount should be removed from there if they werent | 317 | # FIXME: Dust amount should be removed from there if they werent |
317 | # honored in other sales | 318 | # honored in other sales |
318 | delta_in_base = abs(self.delta) | 319 | delta_in_base = abs(self.delta) |
319 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) | 320 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) |
320 | 321 | ||
321 | if not inverted: | 322 | if not self.inverted: |
322 | base_currency = self.base_currency | 323 | base_currency = self.base_currency |
323 | # BTC | 324 | # BTC |
324 | if self.action == "dispose": | 325 | if self.action == "dispose": |
@@ -376,7 +377,7 @@ class Trade: | |||
376 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) | 377 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) |
377 | return None | 378 | return None |
378 | 379 | ||
379 | order = Order(self.order_action(inverted), | 380 | order = Order(self.order_action(), |
380 | delta, rate, base_currency, self.trade_type, | 381 | delta, rate, base_currency, self.trade_type, |
381 | self.market, self, close_if_possible=close_if_possible) | 382 | self.market, self, close_if_possible=close_if_possible) |
382 | self.orders.append(order) | 383 | self.orders.append(order) |
@@ -472,7 +473,7 @@ class Order: | |||
472 | 473 | ||
473 | @property | 474 | @property |
474 | def finished(self): | 475 | def finished(self): |
475 | return self.status == "closed" or self.status == "canceled" or self.status == "error" | 476 | return self.status.startswith("closed") or self.status == "canceled" or self.status == "error" |
476 | 477 | ||
477 | @retry(InsufficientFunds) | 478 | @retry(InsufficientFunds) |
478 | def run(self): | 479 | def run(self): |
@@ -516,15 +517,13 @@ class Order: | |||
516 | # other states are "closed" and "canceled" | 517 | # other states are "closed" and "canceled" |
517 | if not self.finished: | 518 | if not self.finished: |
518 | self.fetch() | 519 | self.fetch() |
519 | if self.finished: | ||
520 | self.mark_finished_order() | ||
521 | return self.status | 520 | return self.status |
522 | 521 | ||
523 | def mark_finished_order(self): | 522 | def mark_finished_order(self): |
524 | if self.market.debug: | 523 | if self.status.startswith("closed") and self.market.debug: |
525 | self.market.report.log_debug_action("Mark {} as finished".format(self)) | 524 | self.market.report.log_debug_action("Mark {} as finished".format(self)) |
526 | return | 525 | return |
527 | if self.status == "closed": | 526 | if self.status.startswith("closed"): |
528 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: | 527 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: |
529 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) | 528 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) |
530 | 529 | ||
@@ -543,6 +542,7 @@ class Order: | |||
543 | 542 | ||
544 | self.fetch_mouvements() | 543 | self.fetch_mouvements() |
545 | 544 | ||
545 | self.mark_finished_order() | ||
546 | # FIXME: consider open order with dust remaining as closed | 546 | # FIXME: consider open order with dust remaining as closed |
547 | 547 | ||
548 | def dust_amount_remaining(self): | 548 | def dust_amount_remaining(self): |
@@ -2007,16 +2007,20 @@ class TradeTest(WebMockTestCase): | |||
2007 | value_to = portfolio.Amount("BTC", "1.0") | 2007 | value_to = portfolio.Amount("BTC", "1.0") |
2008 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 2008 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) |
2009 | 2009 | ||
2010 | self.assertEqual("buy", trade.order_action(False)) | 2010 | trade.inverted = False |
2011 | self.assertEqual("sell", trade.order_action(True)) | 2011 | self.assertEqual("buy", trade.order_action()) |
2012 | trade.inverted = True | ||
2013 | self.assertEqual("sell", trade.order_action()) | ||
2012 | 2014 | ||
2013 | value_from = portfolio.Amount("BTC", "0") | 2015 | value_from = portfolio.Amount("BTC", "0") |
2014 | value_from.linked_to = portfolio.Amount("ETH", "0") | 2016 | value_from.linked_to = portfolio.Amount("ETH", "0") |
2015 | value_to = portfolio.Amount("BTC", "-1.0") | 2017 | value_to = portfolio.Amount("BTC", "-1.0") |
2016 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 2018 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) |
2017 | 2019 | ||
2018 | self.assertEqual("sell", trade.order_action(False)) | 2020 | trade.inverted = False |
2019 | self.assertEqual("buy", trade.order_action(True)) | 2021 | self.assertEqual("sell", trade.order_action()) |
2022 | trade.inverted = True | ||
2023 | self.assertEqual("buy", trade.order_action()) | ||
2020 | 2024 | ||
2021 | def test_trade_type(self): | 2025 | def test_trade_type(self): |
2022 | value_from = portfolio.Amount("BTC", "0.5") | 2026 | value_from = portfolio.Amount("BTC", "0.5") |
@@ -2034,26 +2038,59 @@ class TradeTest(WebMockTestCase): | |||
2034 | self.assertEqual("short", trade.trade_type) | 2038 | self.assertEqual("short", trade.trade_type) |
2035 | 2039 | ||
2036 | def test_is_fullfiled(self): | 2040 | def test_is_fullfiled(self): |
2037 | value_from = portfolio.Amount("BTC", "0.5") | 2041 | with self.subTest(inverted=False): |
2038 | value_from.linked_to = portfolio.Amount("ETH", "10.0") | 2042 | value_from = portfolio.Amount("BTC", "0.5") |
2039 | value_to = portfolio.Amount("BTC", "1.0") | 2043 | value_from.linked_to = portfolio.Amount("ETH", "10.0") |
2040 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 2044 | value_to = portfolio.Amount("BTC", "1.0") |
2045 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | ||
2041 | 2046 | ||
2042 | order1 = mock.Mock() | 2047 | order1 = mock.Mock() |
2043 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") | 2048 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") |
2044 | 2049 | ||
2045 | order2 = mock.Mock() | 2050 | order2 = mock.Mock() |
2046 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") | 2051 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") |
2047 | trade.orders.append(order1) | 2052 | trade.orders.append(order1) |
2048 | trade.orders.append(order2) | 2053 | trade.orders.append(order2) |
2054 | |||
2055 | self.assertFalse(trade.is_fullfiled) | ||
2056 | |||
2057 | order3 = mock.Mock() | ||
2058 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | ||
2059 | trade.orders.append(order3) | ||
2060 | |||
2061 | self.assertTrue(trade.is_fullfiled) | ||
2062 | |||
2063 | order1.filled_amount.assert_called_with(in_base_currency=True) | ||
2064 | order2.filled_amount.assert_called_with(in_base_currency=True) | ||
2065 | order3.filled_amount.assert_called_with(in_base_currency=True) | ||
2049 | 2066 | ||
2050 | self.assertFalse(trade.is_fullfiled) | 2067 | with self.subTest(inverted=True): |
2068 | value_from = portfolio.Amount("BTC", "0.5") | ||
2069 | value_from.linked_to = portfolio.Amount("USDT", "1000.0") | ||
2070 | value_to = portfolio.Amount("BTC", "1.0") | ||
2071 | trade = portfolio.Trade(value_from, value_to, "USDT", self.m) | ||
2072 | trade.inverted = True | ||
2051 | 2073 | ||
2052 | order3 = mock.Mock() | 2074 | order1 = mock.Mock() |
2053 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | 2075 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") |
2054 | trade.orders.append(order3) | 2076 | |
2077 | order2 = mock.Mock() | ||
2078 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") | ||
2079 | trade.orders.append(order1) | ||
2080 | trade.orders.append(order2) | ||
2081 | |||
2082 | self.assertFalse(trade.is_fullfiled) | ||
2083 | |||
2084 | order3 = mock.Mock() | ||
2085 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | ||
2086 | trade.orders.append(order3) | ||
2087 | |||
2088 | self.assertTrue(trade.is_fullfiled) | ||
2089 | |||
2090 | order1.filled_amount.assert_called_with(in_base_currency=False) | ||
2091 | order2.filled_amount.assert_called_with(in_base_currency=False) | ||
2092 | order3.filled_amount.assert_called_with(in_base_currency=False) | ||
2055 | 2093 | ||
2056 | self.assertTrue(trade.is_fullfiled) | ||
2057 | 2094 | ||
2058 | def test_filled_amount(self): | 2095 | def test_filled_amount(self): |
2059 | value_from = portfolio.Amount("BTC", "0.5") | 2096 | value_from = portfolio.Amount("BTC", "0.5") |
@@ -2713,7 +2750,8 @@ class OrderTest(WebMockTestCase): | |||
2713 | self.m.report.log_debug_action.assert_called_once() | 2750 | self.m.report.log_debug_action.assert_called_once() |
2714 | 2751 | ||
2715 | @mock.patch.object(portfolio.Order, "fetch_mouvements") | 2752 | @mock.patch.object(portfolio.Order, "fetch_mouvements") |
2716 | def test_fetch(self, fetch_mouvements): | 2753 | @mock.patch.object(portfolio.Order, "mark_finished_order") |
2754 | def test_fetch(self, mark_finished_order, fetch_mouvements): | ||
2717 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 2755 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
2718 | D("0.1"), "BTC", "long", self.m, "trade") | 2756 | D("0.1"), "BTC", "long", self.m, "trade") |
2719 | order.id = 45 | 2757 | order.id = 45 |
@@ -2723,6 +2761,7 @@ class OrderTest(WebMockTestCase): | |||
2723 | self.m.report.log_debug_action.assert_called_once() | 2761 | self.m.report.log_debug_action.assert_called_once() |
2724 | self.m.report.log_debug_action.reset_mock() | 2762 | self.m.report.log_debug_action.reset_mock() |
2725 | self.m.ccxt.fetch_order.assert_not_called() | 2763 | self.m.ccxt.fetch_order.assert_not_called() |
2764 | mark_finished_order.assert_not_called() | ||
2726 | fetch_mouvements.assert_not_called() | 2765 | fetch_mouvements.assert_not_called() |
2727 | 2766 | ||
2728 | with self.subTest(debug=False): | 2767 | with self.subTest(debug=False): |
@@ -2739,17 +2778,19 @@ class OrderTest(WebMockTestCase): | |||
2739 | self.assertEqual("timestamp", order.timestamp) | 2778 | self.assertEqual("timestamp", order.timestamp) |
2740 | self.assertEqual(1, len(order.results)) | 2779 | self.assertEqual(1, len(order.results)) |
2741 | self.m.report.log_debug_action.assert_not_called() | 2780 | self.m.report.log_debug_action.assert_not_called() |
2781 | mark_finished_order.assert_called_once() | ||
2742 | 2782 | ||
2783 | mark_finished_order.reset_mock() | ||
2743 | with self.subTest(missing_order=True): | 2784 | with self.subTest(missing_order=True): |
2744 | self.m.ccxt.fetch_order.side_effect = [ | 2785 | self.m.ccxt.fetch_order.side_effect = [ |
2745 | portfolio.OrderNotCached, | 2786 | portfolio.OrderNotCached, |
2746 | ] | 2787 | ] |
2747 | order.fetch() | 2788 | order.fetch() |
2748 | self.assertEqual("closed_unknown", order.status) | 2789 | self.assertEqual("closed_unknown", order.status) |
2790 | mark_finished_order.assert_called_once() | ||
2749 | 2791 | ||
2750 | @mock.patch.object(portfolio.Order, "fetch") | 2792 | @mock.patch.object(portfolio.Order, "fetch") |
2751 | @mock.patch.object(portfolio.Order, "mark_finished_order") | 2793 | def test_get_status(self, fetch): |
2752 | def test_get_status(self, mark_finished_order, fetch): | ||
2753 | with self.subTest(debug=True): | 2794 | with self.subTest(debug=True): |
2754 | self.m.debug = True | 2795 | self.m.debug = True |
2755 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 2796 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
@@ -2768,10 +2809,8 @@ class OrderTest(WebMockTestCase): | |||
2768 | return update_status | 2809 | return update_status |
2769 | fetch.side_effect = _fetch(order) | 2810 | fetch.side_effect = _fetch(order) |
2770 | self.assertEqual("open", order.get_status()) | 2811 | self.assertEqual("open", order.get_status()) |
2771 | mark_finished_order.assert_not_called() | ||
2772 | fetch.assert_called_once() | 2812 | fetch.assert_called_once() |
2773 | 2813 | ||
2774 | mark_finished_order.reset_mock() | ||
2775 | fetch.reset_mock() | 2814 | fetch.reset_mock() |
2776 | with self.subTest(debug=False, finished=True): | 2815 | with self.subTest(debug=False, finished=True): |
2777 | self.m.debug = False | 2816 | self.m.debug = False |
@@ -2783,7 +2822,6 @@ class OrderTest(WebMockTestCase): | |||
2783 | return update_status | 2822 | return update_status |
2784 | fetch.side_effect = _fetch(order) | 2823 | fetch.side_effect = _fetch(order) |
2785 | self.assertEqual("closed", order.get_status()) | 2824 | self.assertEqual("closed", order.get_status()) |
2786 | mark_finished_order.assert_called_once() | ||
2787 | fetch.assert_called_once() | 2825 | fetch.assert_called_once() |
2788 | 2826 | ||
2789 | def test_run(self): | 2827 | def test_run(self): |