diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-27 00:58:52 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-02-27 00:58:52 +0100 |
commit | 2033e7fef780298be2ec15455a0ec1d26515de55 (patch) | |
tree | 7650db089756994dfbc0b77cad76fe662213e702 | |
parent | f70bb858007cd3be6766ee0aa4a3d9133952eb98 (diff) | |
download | Trader-2033e7fef780298be2ec15455a0ec1d26515de55.tar.gz Trader-2033e7fef780298be2ec15455a0ec1d26515de55.tar.zst Trader-2033e7fef780298be2ec15455a0ec1d26515de55.zip |
Add make_order and get_user_market helpers
Fix cancel order not actually fetching the order
Fetch only necessary order to poloniex
-rw-r--r-- | helper.py | 56 | ||||
-rw-r--r-- | portfolio.py | 9 | ||||
-rw-r--r-- | test.py | 140 |
3 files changed, 197 insertions, 8 deletions
@@ -7,6 +7,62 @@ import sys | |||
7 | 7 | ||
8 | import portfolio | 8 | import portfolio |
9 | 9 | ||
10 | def make_order(market, value, currency, action="acquire", | ||
11 | close_if_possible=False, base_currency="BTC", follow=True, | ||
12 | compute_value="average"): | ||
13 | """ | ||
14 | Make an order on market | ||
15 | "market": The market on which to place the order | ||
16 | "value": The value in *base_currency* to acquire, | ||
17 | or in *currency* to dispose. | ||
18 | use negative for margin trade. | ||
19 | "action": "acquire" or "dispose". | ||
20 | "acquire" will buy long or sell short, | ||
21 | "dispose" will sell long or buy short. | ||
22 | "currency": The currency to acquire or dispose | ||
23 | "base_currency": The base currency. The value is expressed in that | ||
24 | currency (default: BTC) | ||
25 | "follow": Whether to follow the order once run (default: True) | ||
26 | "close_if_possible": Whether to try to close the position at the end | ||
27 | of the trade, i.e. reach exactly 0 at the end | ||
28 | (only meaningful in "dispose"). May have | ||
29 | unwanted effects if the end value of the | ||
30 | currency is not 0. | ||
31 | "compute_value": Compute value to place the order | ||
32 | """ | ||
33 | market.report.log_stage("make_order_begin") | ||
34 | market.balances.fetch_balances(tag="make_order_begin") | ||
35 | if action == "acquire": | ||
36 | trade = portfolio.Trade( | ||
37 | portfolio.Amount(base_currency, 0), | ||
38 | portfolio.Amount(base_currency, value), | ||
39 | currency, market) | ||
40 | else: | ||
41 | amount = portfolio.Amount(currency, value) | ||
42 | trade = portfolio.Trade( | ||
43 | amount.in_currency(base_currency, market, compute_value=compute_value), | ||
44 | portfolio.Amount(base_currency, 0), | ||
45 | currency, market) | ||
46 | market.trades.all.append(trade) | ||
47 | order = trade.prepare_order( | ||
48 | close_if_possible=close_if_possible, | ||
49 | compute_value=compute_value) | ||
50 | market.report.log_orders([order], None, compute_value) | ||
51 | market.trades.run_orders() | ||
52 | if follow: | ||
53 | market.follow_orders() | ||
54 | market.balances.fetch_balances(tag="make_order_end") | ||
55 | else: | ||
56 | market.report.log_stage("make_order_end_not_followed") | ||
57 | return order | ||
58 | market.report.log_stage("make_order_end") | ||
59 | |||
60 | def get_user_market(config_path, user_id, debug=False): | ||
61 | import market | ||
62 | pg_config, report_path = main_parse_config(config_path) | ||
63 | market_config = list(main_fetch_markets(pg_config, str(user_id)))[0][0] | ||
64 | return market.Market.from_config(market_config, debug=debug) | ||
65 | |||
10 | def main_parse_args(argv): | 66 | def main_parse_args(argv): |
11 | parser = argparse.ArgumentParser( | 67 | parser = argparse.ArgumentParser( |
12 | description="Run the trade bot") | 68 | description="Run the trade bot") |
diff --git a/portfolio.py b/portfolio.py index 6763fc6..eb3390e 100644 --- a/portfolio.py +++ b/portfolio.py | |||
@@ -360,7 +360,7 @@ class Trade: | |||
360 | new_order.run() | 360 | new_order.run() |
361 | self.market.report.log_order(order, tick, new_order=new_order) | 361 | self.market.report.log_order(order, tick, new_order=new_order) |
362 | 362 | ||
363 | def prepare_order(self, compute_value="default"): | 363 | def prepare_order(self, close_if_possible=None, compute_value="default"): |
364 | if self.action is None: | 364 | if self.action is None: |
365 | return None | 365 | return None |
366 | ticker = self.market.get_ticker(self.currency, self.base_currency) | 366 | ticker = self.market.get_ticker(self.currency, self.base_currency) |
@@ -426,7 +426,8 @@ class Trade: | |||
426 | delta = delta - filled | 426 | delta = delta - filled |
427 | # I already sold 4 BTC, only 5 left | 427 | # I already sold 4 BTC, only 5 left |
428 | 428 | ||
429 | close_if_possible = (self.value_to == 0) | 429 | if close_if_possible is None: |
430 | close_if_possible = (self.value_to == 0) | ||
430 | 431 | ||
431 | if delta <= 0: | 432 | if delta <= 0: |
432 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) | 433 | self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) |
@@ -586,7 +587,7 @@ class Order: | |||
586 | return | 587 | return |
587 | self.fetch_cache_timestamp = time.time() | 588 | self.fetch_cache_timestamp = time.time() |
588 | 589 | ||
589 | result = self.market.ccxt.fetch_order(self.id) | 590 | result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency) |
590 | self.results.append(result) | 591 | self.results.append(result) |
591 | 592 | ||
592 | self.status = result["status"] | 593 | self.status = result["status"] |
@@ -632,7 +633,7 @@ class Order: | |||
632 | self.status = "canceled" | 633 | self.status = "canceled" |
633 | return | 634 | return |
634 | self.market.ccxt.cancel_order(self.id) | 635 | self.market.ccxt.cancel_order(self.id) |
635 | self.fetch() | 636 | self.fetch(force=True) |
636 | 637 | ||
637 | class Mouvement: | 638 | class Mouvement: |
638 | def __init__(self, currency, base_currency, hash_): | 639 | def __init__(self, currency, base_currency, hash_): |
@@ -1458,6 +1458,25 @@ class TradeTest(WebMockTestCase): | |||
1458 | D("0.125"), "BTC", "long", self.m, | 1458 | D("0.125"), "BTC", "long", self.m, |
1459 | trade, close_if_possible=False) | 1459 | trade, close_if_possible=False) |
1460 | 1460 | ||
1461 | with self.subTest(action="dispose", inverted=False, close_if_possible=True): | ||
1462 | filled_amount.return_value = portfolio.Amount("FOO", "60") | ||
1463 | compute_value.return_value = D("0.125") | ||
1464 | |||
1465 | value_from = portfolio.Amount("BTC", "10") | ||
1466 | value_from.rate = D("0.1") | ||
1467 | value_from.linked_to = portfolio.Amount("FOO", "100") | ||
1468 | value_to = portfolio.Amount("BTC", "1") | ||
1469 | trade = portfolio.Trade(value_from, value_to, "FOO", self.m) | ||
1470 | |||
1471 | trade.prepare_order(close_if_possible=True) | ||
1472 | |||
1473 | filled_amount.assert_called_with(in_base_currency=False) | ||
1474 | compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default") | ||
1475 | self.assertEqual(1, len(trade.orders)) | ||
1476 | Order.assert_called_with("sell", portfolio.Amount("FOO", 30), | ||
1477 | D("0.125"), "BTC", "long", self.m, | ||
1478 | trade, close_if_possible=True) | ||
1479 | |||
1461 | with self.subTest(action="acquire", inverted=False): | 1480 | with self.subTest(action="acquire", inverted=False): |
1462 | filled_amount.return_value = portfolio.Amount("BTC", "3") | 1481 | filled_amount.return_value = portfolio.Amount("BTC", "3") |
1463 | compute_value.return_value = D("0.125") | 1482 | compute_value.return_value = D("0.125") |
@@ -1812,7 +1831,7 @@ class OrderTest(WebMockTestCase): | |||
1812 | 1831 | ||
1813 | order.cancel() | 1832 | order.cancel() |
1814 | self.m.ccxt.cancel_order.assert_called_with(42) | 1833 | self.m.ccxt.cancel_order.assert_called_with(42) |
1815 | fetch.assert_called_once() | 1834 | fetch.assert_called_once_with(force=True) |
1816 | self.m.report.log_debug_action.assert_not_called() | 1835 | self.m.report.log_debug_action.assert_not_called() |
1817 | 1836 | ||
1818 | def test_dust_amount_remaining(self): | 1837 | def test_dust_amount_remaining(self): |
@@ -1966,7 +1985,7 @@ class OrderTest(WebMockTestCase): | |||
1966 | } | 1985 | } |
1967 | order.fetch() | 1986 | order.fetch() |
1968 | 1987 | ||
1969 | self.m.ccxt.fetch_order.assert_called_once() | 1988 | self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") |
1970 | fetch_mouvements.assert_called_once() | 1989 | fetch_mouvements.assert_called_once() |
1971 | self.assertEqual("foo", order.status) | 1990 | self.assertEqual("foo", order.status) |
1972 | self.assertEqual("timestamp", order.timestamp) | 1991 | self.assertEqual("timestamp", order.timestamp) |
@@ -1982,7 +2001,7 @@ class OrderTest(WebMockTestCase): | |||
1982 | fetch_mouvements.assert_not_called() | 2001 | fetch_mouvements.assert_not_called() |
1983 | 2002 | ||
1984 | order.fetch(force=True) | 2003 | order.fetch(force=True) |
1985 | self.m.ccxt.fetch_order.assert_called_once() | 2004 | self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") |
1986 | fetch_mouvements.assert_called_once() | 2005 | fetch_mouvements.assert_called_once() |
1987 | 2006 | ||
1988 | self.m.ccxt.fetch_order.reset_mock() | 2007 | self.m.ccxt.fetch_order.reset_mock() |
@@ -1990,7 +2009,7 @@ class OrderTest(WebMockTestCase): | |||
1990 | 2009 | ||
1991 | time_mock.return_value = time + 19 | 2010 | time_mock.return_value = time + 19 |
1992 | order.fetch() | 2011 | order.fetch() |
1993 | self.m.ccxt.fetch_order.assert_called_once() | 2012 | self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH") |
1994 | fetch_mouvements.assert_called_once() | 2013 | fetch_mouvements.assert_called_once() |
1995 | self.m.report.log_debug_action.assert_not_called() | 2014 | self.m.report.log_debug_action.assert_not_called() |
1996 | 2015 | ||
@@ -2586,6 +2605,119 @@ class ReportStoreTest(WebMockTestCase): | |||
2586 | 2605 | ||
2587 | @unittest.skipUnless("unit" in limits, "Unit skipped") | 2606 | @unittest.skipUnless("unit" in limits, "Unit skipped") |
2588 | class HelperTest(WebMockTestCase): | 2607 | class HelperTest(WebMockTestCase): |
2608 | def test_make_order(self): | ||
2609 | self.m.get_ticker.return_value = { | ||
2610 | "inverted": False, | ||
2611 | "average": D("0.1"), | ||
2612 | "bid": D("0.09"), | ||
2613 | "ask": D("0.11"), | ||
2614 | } | ||
2615 | |||
2616 | with self.subTest(description="nominal case"): | ||
2617 | helper.make_order(self.m, 10, "ETH") | ||
2618 | |||
2619 | self.m.report.log_stage.assert_has_calls([ | ||
2620 | mock.call("make_order_begin"), | ||
2621 | mock.call("make_order_end"), | ||
2622 | ]) | ||
2623 | self.m.balances.fetch_balances.assert_has_calls([ | ||
2624 | mock.call(tag="make_order_begin"), | ||
2625 | mock.call(tag="make_order_end"), | ||
2626 | ]) | ||
2627 | self.m.trades.all.append.assert_called_once() | ||
2628 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2629 | self.assertEqual(False, trade.orders[0].close_if_possible) | ||
2630 | self.assertEqual(0, trade.value_from) | ||
2631 | self.assertEqual("ETH", trade.currency) | ||
2632 | self.assertEqual("BTC", trade.base_currency) | ||
2633 | self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") | ||
2634 | self.m.trades.run_orders.assert_called_once_with() | ||
2635 | self.m.follow_orders.assert_called_once_with() | ||
2636 | |||
2637 | order = trade.orders[0] | ||
2638 | self.assertEqual(D("0.10"), order.rate) | ||
2639 | |||
2640 | self.m.reset_mock() | ||
2641 | with self.subTest(compute_value="default"): | ||
2642 | helper.make_order(self.m, 10, "ETH", action="dispose", | ||
2643 | compute_value="ask") | ||
2644 | |||
2645 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2646 | order = trade.orders[0] | ||
2647 | self.assertEqual(D("0.11"), order.rate) | ||
2648 | |||
2649 | self.m.reset_mock() | ||
2650 | with self.subTest(follow=False): | ||
2651 | result = helper.make_order(self.m, 10, "ETH", follow=False) | ||
2652 | |||
2653 | self.m.report.log_stage.assert_has_calls([ | ||
2654 | mock.call("make_order_begin"), | ||
2655 | mock.call("make_order_end_not_followed"), | ||
2656 | ]) | ||
2657 | self.m.balances.fetch_balances.assert_called_once_with(tag="make_order_begin") | ||
2658 | |||
2659 | self.m.trades.all.append.assert_called_once() | ||
2660 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2661 | self.assertEqual(0, trade.value_from) | ||
2662 | self.assertEqual("ETH", trade.currency) | ||
2663 | self.assertEqual("BTC", trade.base_currency) | ||
2664 | self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") | ||
2665 | self.m.trades.run_orders.assert_called_once_with() | ||
2666 | self.m.follow_orders.assert_not_called() | ||
2667 | self.assertEqual(trade.orders[0], result) | ||
2668 | |||
2669 | self.m.reset_mock() | ||
2670 | with self.subTest(base_currency="USDT"): | ||
2671 | helper.make_order(self.m, 1, "BTC", base_currency="USDT") | ||
2672 | |||
2673 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2674 | self.assertEqual("BTC", trade.currency) | ||
2675 | self.assertEqual("USDT", trade.base_currency) | ||
2676 | |||
2677 | self.m.reset_mock() | ||
2678 | with self.subTest(close_if_possible=True): | ||
2679 | helper.make_order(self.m, 10, "ETH", close_if_possible=True) | ||
2680 | |||
2681 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2682 | self.assertEqual(True, trade.orders[0].close_if_possible) | ||
2683 | |||
2684 | self.m.reset_mock() | ||
2685 | with self.subTest(action="dispose"): | ||
2686 | helper.make_order(self.m, 10, "ETH", action="dispose") | ||
2687 | |||
2688 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2689 | self.assertEqual(0, trade.value_to) | ||
2690 | self.assertEqual(1, trade.value_from.value) | ||
2691 | self.assertEqual("ETH", trade.currency) | ||
2692 | self.assertEqual("BTC", trade.base_currency) | ||
2693 | |||
2694 | self.m.reset_mock() | ||
2695 | with self.subTest(compute_value="default"): | ||
2696 | helper.make_order(self.m, 10, "ETH", action="dispose", | ||
2697 | compute_value="bid") | ||
2698 | |||
2699 | trade = self.m.trades.all.append.mock_calls[0][1][0] | ||
2700 | self.assertEqual(D("0.9"), trade.value_from.value) | ||
2701 | |||
2702 | def test_user_market(self): | ||
2703 | with mock.patch("helper.main_fetch_markets") as main_fetch_markets,\ | ||
2704 | mock.patch("helper.main_parse_config") as main_parse_config: | ||
2705 | with self.subTest(debug=False): | ||
2706 | main_parse_config.return_value = ["pg_config", "report_path"] | ||
2707 | main_fetch_markets.return_value = [({"key": "market_config"},)] | ||
2708 | m = helper.get_user_market("config_path.ini", 1) | ||
2709 | |||
2710 | self.assertIsInstance(m, market.Market) | ||
2711 | self.assertFalse(m.debug) | ||
2712 | |||
2713 | with self.subTest(debug=True): | ||
2714 | main_parse_config.return_value = ["pg_config", "report_path"] | ||
2715 | main_fetch_markets.return_value = [({"key": "market_config"},)] | ||
2716 | m = helper.get_user_market("config_path.ini", 1, debug=True) | ||
2717 | |||
2718 | self.assertIsInstance(m, market.Market) | ||
2719 | self.assertTrue(m.debug) | ||
2720 | |||
2589 | def test_main_store_report(self): | 2721 | def test_main_store_report(self): |
2590 | file_open = mock.mock_open() | 2722 | file_open = mock.mock_open() |
2591 | with self.subTest(file=None), mock.patch("__main__.open", file_open): | 2723 | with self.subTest(file=None), mock.patch("__main__.open", file_open): |