diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-12 02:06:42 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-12 02:06:42 +0100 |
commit | 3faa0d739e599daafacf948534d2e66292387d02 (patch) | |
tree | f56ae35251cfeb60c563c023f7f48e59f8dbee7e | |
parent | f9226903cb53a9b303a26de562e321159349f8df (diff) | |
parent | d8deb0e9a6b7b2805e61dc19a287d5474c271cc5 (diff) | |
download | Trader-3faa0d739e599daafacf948534d2e66292387d02.tar.gz Trader-3faa0d739e599daafacf948534d2e66292387d02.tar.zst Trader-3faa0d739e599daafacf948534d2e66292387d02.zip |
Merge branch 'night_fixes'v0.5.1
-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 0f2c011..ed50b57 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -291,6 +291,7 @@ class Trade: | |||
291 | self.orders = [] | 291 | self.orders = [] |
292 | self.market = market | 292 | self.market = market |
293 | self.closed = False | 293 | self.closed = False |
294 | self.inverted = None | ||
294 | assert self.value_from.value * self.value_to.value >= 0 | 295 | assert self.value_from.value * self.value_to.value >= 0 |
295 | assert self.value_from.currency == self.value_to.currency | 296 | assert self.value_from.currency == self.value_to.currency |
296 | if self.value_from != 0: | 297 | if self.value_from != 0: |
@@ -315,8 +316,8 @@ class Trade: | |||
315 | else: | 316 | else: |
316 | return "dispose" | 317 | return "dispose" |
317 | 318 | ||
318 | def order_action(self, inverted): | 319 | def order_action(self): |
319 | if (self.value_from < self.value_to) != inverted: | 320 | if (self.value_from < self.value_to) != self.inverted: |
320 | return "buy" | 321 | return "buy" |
321 | else: | 322 | else: |
322 | return "sell" | 323 | return "sell" |
@@ -339,7 +340,7 @@ class Trade: | |||
339 | 340 | ||
340 | @property | 341 | @property |
341 | def is_fullfiled(self): | 342 | def is_fullfiled(self): |
342 | return abs(self.filled_amount(in_base_currency=True)) >= abs(self.delta) | 343 | return abs(self.filled_amount(in_base_currency=(not self.inverted))) >= abs(self.delta) |
343 | 344 | ||
344 | def filled_amount(self, in_base_currency=False): | 345 | def filled_amount(self, in_base_currency=False): |
345 | filled_amount = 0 | 346 | filled_amount = 0 |
@@ -385,17 +386,17 @@ class Trade: | |||
385 | if self.action is None: | 386 | if self.action is None: |
386 | return None | 387 | return None |
387 | ticker = self.market.get_ticker(self.currency, self.base_currency) | 388 | ticker = self.market.get_ticker(self.currency, self.base_currency) |
388 | inverted = ticker["inverted"] | 389 | self.inverted = ticker["inverted"] |
389 | if inverted: | 390 | if self.inverted: |
390 | ticker = ticker["original"] | 391 | ticker = ticker["original"] |
391 | rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) | 392 | rate = Computation.compute_value(ticker, self.order_action(), compute_value=compute_value) |
392 | 393 | ||
393 | # FIXME: Dust amount should be removed from there if they werent | 394 | # FIXME: Dust amount should be removed from there if they werent |
394 | # honored in other sales | 395 | # honored in other sales |
395 | delta_in_base = abs(self.delta) | 396 | delta_in_base = abs(self.delta) |
396 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) | 397 | # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) |
397 | 398 | ||
398 | if not inverted: | 399 | if not self.inverted: |
399 | base_currency = self.base_currency | 400 | base_currency = self.base_currency |
400 | # BTC | 401 | # BTC |
401 | if self.action == "dispose": | 402 | if self.action == "dispose": |
@@ -453,7 +454,7 @@ class Trade: | |||
453 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) | 454 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) |
454 | return None | 455 | return None |
455 | 456 | ||
456 | order = Order(self.order_action(inverted), | 457 | order = Order(self.order_action(), |
457 | delta, rate, base_currency, self.trade_type, | 458 | delta, rate, base_currency, self.trade_type, |
458 | self.market, self, close_if_possible=close_if_possible) | 459 | self.market, self, close_if_possible=close_if_possible) |
459 | self.orders.append(order) | 460 | self.orders.append(order) |
@@ -549,7 +550,7 @@ class Order: | |||
549 | 550 | ||
550 | @property | 551 | @property |
551 | def finished(self): | 552 | def finished(self): |
552 | return self.status == "closed" or self.status == "canceled" or self.status == "error" | 553 | return self.status.startswith("closed") or self.status == "canceled" or self.status == "error" |
553 | 554 | ||
554 | @retry(InsufficientFunds) | 555 | @retry(InsufficientFunds) |
555 | def run(self): | 556 | def run(self): |
@@ -593,15 +594,13 @@ class Order: | |||
593 | # other states are "closed" and "canceled" | 594 | # other states are "closed" and "canceled" |
594 | if not self.finished: | 595 | if not self.finished: |
595 | self.fetch() | 596 | self.fetch() |
596 | if self.finished: | ||
597 | self.mark_finished_order() | ||
598 | return self.status | 597 | return self.status |
599 | 598 | ||
600 | def mark_finished_order(self): | 599 | def mark_finished_order(self): |
601 | if self.market.debug: | 600 | if self.status.startswith("closed") and self.market.debug: |
602 | self.market.report.log_debug_action("Mark {} as finished".format(self)) | 601 | self.market.report.log_debug_action("Mark {} as finished".format(self)) |
603 | return | 602 | return |
604 | if self.status == "closed": | 603 | if self.status.startswith("closed"): |
605 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: | 604 | if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: |
606 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) | 605 | self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency) |
607 | 606 | ||
@@ -620,6 +619,7 @@ class Order: | |||
620 | 619 | ||
621 | self.fetch_mouvements() | 620 | self.fetch_mouvements() |
622 | 621 | ||
622 | self.mark_finished_order() | ||
623 | # FIXME: consider open order with dust remaining as closed | 623 | # FIXME: consider open order with dust remaining as closed |
624 | 624 | ||
625 | def dust_amount_remaining(self): | 625 | def dust_amount_remaining(self): |
@@ -1375,16 +1375,20 @@ class TradeTest(WebMockTestCase): | |||
1375 | value_to = portfolio.Amount("BTC", "1.0") | 1375 | value_to = portfolio.Amount("BTC", "1.0") |
1376 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 1376 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) |
1377 | 1377 | ||
1378 | self.assertEqual("buy", trade.order_action(False)) | 1378 | trade.inverted = False |
1379 | self.assertEqual("sell", trade.order_action(True)) | 1379 | self.assertEqual("buy", trade.order_action()) |
1380 | trade.inverted = True | ||
1381 | self.assertEqual("sell", trade.order_action()) | ||
1380 | 1382 | ||
1381 | value_from = portfolio.Amount("BTC", "0") | 1383 | value_from = portfolio.Amount("BTC", "0") |
1382 | value_from.linked_to = portfolio.Amount("ETH", "0") | 1384 | value_from.linked_to = portfolio.Amount("ETH", "0") |
1383 | value_to = portfolio.Amount("BTC", "-1.0") | 1385 | value_to = portfolio.Amount("BTC", "-1.0") |
1384 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 1386 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) |
1385 | 1387 | ||
1386 | self.assertEqual("sell", trade.order_action(False)) | 1388 | trade.inverted = False |
1387 | self.assertEqual("buy", trade.order_action(True)) | 1389 | self.assertEqual("sell", trade.order_action()) |
1390 | trade.inverted = True | ||
1391 | self.assertEqual("buy", trade.order_action()) | ||
1388 | 1392 | ||
1389 | def test_trade_type(self): | 1393 | def test_trade_type(self): |
1390 | value_from = portfolio.Amount("BTC", "0.5") | 1394 | value_from = portfolio.Amount("BTC", "0.5") |
@@ -1402,26 +1406,59 @@ class TradeTest(WebMockTestCase): | |||
1402 | self.assertEqual("short", trade.trade_type) | 1406 | self.assertEqual("short", trade.trade_type) |
1403 | 1407 | ||
1404 | def test_is_fullfiled(self): | 1408 | def test_is_fullfiled(self): |
1405 | value_from = portfolio.Amount("BTC", "0.5") | 1409 | with self.subTest(inverted=False): |
1406 | value_from.linked_to = portfolio.Amount("ETH", "10.0") | 1410 | value_from = portfolio.Amount("BTC", "0.5") |
1407 | value_to = portfolio.Amount("BTC", "1.0") | 1411 | value_from.linked_to = portfolio.Amount("ETH", "10.0") |
1408 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | 1412 | value_to = portfolio.Amount("BTC", "1.0") |
1413 | trade = portfolio.Trade(value_from, value_to, "ETH", self.m) | ||
1409 | 1414 | ||
1410 | order1 = mock.Mock() | 1415 | order1 = mock.Mock() |
1411 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") | 1416 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") |
1412 | 1417 | ||
1413 | order2 = mock.Mock() | 1418 | order2 = mock.Mock() |
1414 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") | 1419 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") |
1415 | trade.orders.append(order1) | 1420 | trade.orders.append(order1) |
1416 | trade.orders.append(order2) | 1421 | trade.orders.append(order2) |
1422 | |||
1423 | self.assertFalse(trade.is_fullfiled) | ||
1424 | |||
1425 | order3 = mock.Mock() | ||
1426 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | ||
1427 | trade.orders.append(order3) | ||
1428 | |||
1429 | self.assertTrue(trade.is_fullfiled) | ||
1430 | |||
1431 | order1.filled_amount.assert_called_with(in_base_currency=True) | ||
1432 | order2.filled_amount.assert_called_with(in_base_currency=True) | ||
1433 | order3.filled_amount.assert_called_with(in_base_currency=True) | ||
1417 | 1434 | ||
1418 | self.assertFalse(trade.is_fullfiled) | 1435 | with self.subTest(inverted=True): |
1436 | value_from = portfolio.Amount("BTC", "0.5") | ||
1437 | value_from.linked_to = portfolio.Amount("USDT", "1000.0") | ||
1438 | value_to = portfolio.Amount("BTC", "1.0") | ||
1439 | trade = portfolio.Trade(value_from, value_to, "USDT", self.m) | ||
1440 | trade.inverted = True | ||
1419 | 1441 | ||
1420 | order3 = mock.Mock() | 1442 | order1 = mock.Mock() |
1421 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | 1443 | order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") |
1422 | trade.orders.append(order3) | 1444 | |
1445 | order2 = mock.Mock() | ||
1446 | order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") | ||
1447 | trade.orders.append(order1) | ||
1448 | trade.orders.append(order2) | ||
1449 | |||
1450 | self.assertFalse(trade.is_fullfiled) | ||
1451 | |||
1452 | order3 = mock.Mock() | ||
1453 | order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") | ||
1454 | trade.orders.append(order3) | ||
1455 | |||
1456 | self.assertTrue(trade.is_fullfiled) | ||
1457 | |||
1458 | order1.filled_amount.assert_called_with(in_base_currency=False) | ||
1459 | order2.filled_amount.assert_called_with(in_base_currency=False) | ||
1460 | order3.filled_amount.assert_called_with(in_base_currency=False) | ||
1423 | 1461 | ||
1424 | self.assertTrue(trade.is_fullfiled) | ||
1425 | 1462 | ||
1426 | def test_filled_amount(self): | 1463 | def test_filled_amount(self): |
1427 | value_from = portfolio.Amount("BTC", "0.5") | 1464 | value_from = portfolio.Amount("BTC", "0.5") |
@@ -2081,7 +2118,8 @@ class OrderTest(WebMockTestCase): | |||
2081 | self.m.report.log_debug_action.assert_called_once() | 2118 | self.m.report.log_debug_action.assert_called_once() |
2082 | 2119 | ||
2083 | @mock.patch.object(portfolio.Order, "fetch_mouvements") | 2120 | @mock.patch.object(portfolio.Order, "fetch_mouvements") |
2084 | def test_fetch(self, fetch_mouvements): | 2121 | @mock.patch.object(portfolio.Order, "mark_finished_order") |
2122 | def test_fetch(self, mark_finished_order, fetch_mouvements): | ||
2085 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 2123 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
2086 | D("0.1"), "BTC", "long", self.m, "trade") | 2124 | D("0.1"), "BTC", "long", self.m, "trade") |
2087 | order.id = 45 | 2125 | order.id = 45 |
@@ -2091,6 +2129,7 @@ class OrderTest(WebMockTestCase): | |||
2091 | self.m.report.log_debug_action.assert_called_once() | 2129 | self.m.report.log_debug_action.assert_called_once() |
2092 | self.m.report.log_debug_action.reset_mock() | 2130 | self.m.report.log_debug_action.reset_mock() |
2093 | self.m.ccxt.fetch_order.assert_not_called() | 2131 | self.m.ccxt.fetch_order.assert_not_called() |
2132 | mark_finished_order.assert_not_called() | ||
2094 | fetch_mouvements.assert_not_called() | 2133 | fetch_mouvements.assert_not_called() |
2095 | 2134 | ||
2096 | with self.subTest(debug=False): | 2135 | with self.subTest(debug=False): |
@@ -2107,17 +2146,19 @@ class OrderTest(WebMockTestCase): | |||
2107 | self.assertEqual("timestamp", order.timestamp) | 2146 | self.assertEqual("timestamp", order.timestamp) |
2108 | self.assertEqual(1, len(order.results)) | 2147 | self.assertEqual(1, len(order.results)) |
2109 | self.m.report.log_debug_action.assert_not_called() | 2148 | self.m.report.log_debug_action.assert_not_called() |
2149 | mark_finished_order.assert_called_once() | ||
2110 | 2150 | ||
2151 | mark_finished_order.reset_mock() | ||
2111 | with self.subTest(missing_order=True): | 2152 | with self.subTest(missing_order=True): |
2112 | self.m.ccxt.fetch_order.side_effect = [ | 2153 | self.m.ccxt.fetch_order.side_effect = [ |
2113 | portfolio.OrderNotCached, | 2154 | portfolio.OrderNotCached, |
2114 | ] | 2155 | ] |
2115 | order.fetch() | 2156 | order.fetch() |
2116 | self.assertEqual("closed_unknown", order.status) | 2157 | self.assertEqual("closed_unknown", order.status) |
2158 | mark_finished_order.assert_called_once() | ||
2117 | 2159 | ||
2118 | @mock.patch.object(portfolio.Order, "fetch") | 2160 | @mock.patch.object(portfolio.Order, "fetch") |
2119 | @mock.patch.object(portfolio.Order, "mark_finished_order") | 2161 | def test_get_status(self, fetch): |
2120 | def test_get_status(self, mark_finished_order, fetch): | ||
2121 | with self.subTest(debug=True): | 2162 | with self.subTest(debug=True): |
2122 | self.m.debug = True | 2163 | self.m.debug = True |
2123 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), | 2164 | order = portfolio.Order("buy", portfolio.Amount("ETH", 10), |
@@ -2136,10 +2177,8 @@ class OrderTest(WebMockTestCase): | |||
2136 | return update_status | 2177 | return update_status |
2137 | fetch.side_effect = _fetch(order) | 2178 | fetch.side_effect = _fetch(order) |
2138 | self.assertEqual("open", order.get_status()) | 2179 | self.assertEqual("open", order.get_status()) |
2139 | mark_finished_order.assert_not_called() | ||
2140 | fetch.assert_called_once() | 2180 | fetch.assert_called_once() |
2141 | 2181 | ||
2142 | mark_finished_order.reset_mock() | ||
2143 | fetch.reset_mock() | 2182 | fetch.reset_mock() |
2144 | with self.subTest(debug=False, finished=True): | 2183 | with self.subTest(debug=False, finished=True): |
2145 | self.m.debug = False | 2184 | self.m.debug = False |
@@ -2151,7 +2190,6 @@ class OrderTest(WebMockTestCase): | |||
2151 | return update_status | 2190 | return update_status |
2152 | fetch.side_effect = _fetch(order) | 2191 | fetch.side_effect = _fetch(order) |
2153 | self.assertEqual("closed", order.get_status()) | 2192 | self.assertEqual("closed", order.get_status()) |
2154 | mark_finished_order.assert_called_once() | ||
2155 | fetch.assert_called_once() | 2193 | fetch.assert_called_once() |
2156 | 2194 | ||
2157 | def test_run(self): | 2195 | def test_run(self): |