aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-27 00:58:52 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-27 00:58:52 +0100
commit2033e7fef780298be2ec15455a0ec1d26515de55 (patch)
tree7650db089756994dfbc0b77cad76fe662213e702
parentf70bb858007cd3be6766ee0aa4a3d9133952eb98 (diff)
downloadTrader-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.py56
-rw-r--r--portfolio.py9
-rw-r--r--test.py140
3 files changed, 197 insertions, 8 deletions
diff --git a/helper.py b/helper.py
index 4d73078..d948dac 100644
--- a/helper.py
+++ b/helper.py
@@ -7,6 +7,62 @@ import sys
7 7
8import portfolio 8import portfolio
9 9
10def 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
60def 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
10def main_parse_args(argv): 66def 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
637class Mouvement: 638class Mouvement:
638 def __init__(self, currency, base_currency, hash_): 639 def __init__(self, currency, base_currency, hash_):
diff --git a/test.py b/test.py
index 955e2a1..778fc14 100644
--- a/test.py
+++ b/test.py
@@ -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")
2588class HelperTest(WebMockTestCase): 2607class 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):