]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/commitdiff
Add make_order and get_user_market helpers
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Mon, 26 Feb 2018 23:58:52 +0000 (00:58 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Mon, 26 Feb 2018 23:58:52 +0000 (00:58 +0100)
Fix cancel order not actually fetching the order
Fetch only necessary order to poloniex

helper.py
portfolio.py
test.py

index 4d73078dbb99aed90f819c0ac9947f04a8f9d080..d948dacd6caadec59c643bcb242c6b3cfd4b7642 100644 (file)
--- a/helper.py
+++ b/helper.py
@@ -7,6 +7,62 @@ import sys
 
 import portfolio
 
+def make_order(market, value, currency, action="acquire",
+        close_if_possible=False, base_currency="BTC", follow=True,
+        compute_value="average"):
+    """
+    Make an order on market
+    "market": The market on which to place the order
+    "value": The value in *base_currency* to acquire,
+             or in *currency* to dispose.
+             use negative for margin trade.
+    "action": "acquire" or "dispose".
+                "acquire" will buy long or sell short,
+                "dispose" will sell long or buy short.
+    "currency": The currency to acquire or dispose
+    "base_currency": The base currency. The value is expressed in that
+                     currency (default: BTC)
+    "follow": Whether to follow the order once run (default: True)
+    "close_if_possible": Whether to try to close the position at the end
+                         of the trade, i.e. reach exactly 0 at the end
+                         (only meaningful in "dispose"). May have
+                         unwanted effects if the end value of the
+                         currency is not 0.
+    "compute_value": Compute value to place the order
+    """
+    market.report.log_stage("make_order_begin")
+    market.balances.fetch_balances(tag="make_order_begin")
+    if action == "acquire":
+        trade = portfolio.Trade(
+                portfolio.Amount(base_currency, 0),
+                portfolio.Amount(base_currency, value),
+                currency, market)
+    else:
+        amount = portfolio.Amount(currency, value)
+        trade = portfolio.Trade(
+                amount.in_currency(base_currency, market, compute_value=compute_value),
+                portfolio.Amount(base_currency, 0),
+                currency, market)
+    market.trades.all.append(trade)
+    order = trade.prepare_order(
+            close_if_possible=close_if_possible,
+            compute_value=compute_value)
+    market.report.log_orders([order], None, compute_value)
+    market.trades.run_orders()
+    if follow:
+        market.follow_orders()
+        market.balances.fetch_balances(tag="make_order_end")
+    else:
+        market.report.log_stage("make_order_end_not_followed")
+        return order
+    market.report.log_stage("make_order_end")
+
+def get_user_market(config_path, user_id, debug=False):
+    import market
+    pg_config, report_path = main_parse_config(config_path)
+    market_config = list(main_fetch_markets(pg_config, str(user_id)))[0][0]
+    return market.Market.from_config(market_config, debug=debug)
+
 def main_parse_args(argv):
     parser = argparse.ArgumentParser(
             description="Run the trade bot")
index 6763fc67a6b56d759deb972cbd75795a424057ce..eb3390ed70d2c156f90002afa994e91a14ddf651 100644 (file)
@@ -360,7 +360,7 @@ class Trade:
             new_order.run()
             self.market.report.log_order(order, tick, new_order=new_order)
 
-    def prepare_order(self, compute_value="default"):
+    def prepare_order(self, close_if_possible=None, compute_value="default"):
         if self.action is None:
             return None
         ticker = self.market.get_ticker(self.currency, self.base_currency)
@@ -426,7 +426,8 @@ class Trade:
                 delta = delta - filled
                 # I already sold 4 BTC, only 5 left
 
-        close_if_possible = (self.value_to == 0)
+        if close_if_possible is None:
+            close_if_possible = (self.value_to == 0)
 
         if delta <= 0:
             self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta))
@@ -586,7 +587,7 @@ class Order:
             return
         self.fetch_cache_timestamp = time.time()
 
-        result = self.market.ccxt.fetch_order(self.id)
+        result = self.market.ccxt.fetch_order(self.id, symbol=self.amount.currency)
         self.results.append(result)
 
         self.status = result["status"]
@@ -632,7 +633,7 @@ class Order:
             self.status = "canceled"
             return
         self.market.ccxt.cancel_order(self.id)
-        self.fetch()
+        self.fetch(force=True)
 
 class Mouvement:
     def __init__(self, currency, base_currency, hash_):
diff --git a/test.py b/test.py
index 955e2a180caf92b2da8211a37c6502dfff58f291..778fc14961cf27011a352624936dcbe0b7780a2f 100644 (file)
--- a/test.py
+++ b/test.py
@@ -1458,6 +1458,25 @@ class TradeTest(WebMockTestCase):
                     D("0.125"), "BTC", "long", self.m,
                     trade, close_if_possible=False)
 
+        with self.subTest(action="dispose", inverted=False, close_if_possible=True):
+            filled_amount.return_value = portfolio.Amount("FOO", "60")
+            compute_value.return_value = D("0.125")
+
+            value_from = portfolio.Amount("BTC", "10")
+            value_from.rate = D("0.1")
+            value_from.linked_to = portfolio.Amount("FOO", "100")
+            value_to = portfolio.Amount("BTC", "1")
+            trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
+
+            trade.prepare_order(close_if_possible=True)
+
+            filled_amount.assert_called_with(in_base_currency=False)
+            compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
+            self.assertEqual(1, len(trade.orders))
+            Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
+                    D("0.125"), "BTC", "long", self.m,
+                    trade, close_if_possible=True)
+
         with self.subTest(action="acquire", inverted=False):
             filled_amount.return_value = portfolio.Amount("BTC", "3")
             compute_value.return_value = D("0.125")
@@ -1812,7 +1831,7 @@ class OrderTest(WebMockTestCase):
 
         order.cancel()
         self.m.ccxt.cancel_order.assert_called_with(42)
-        fetch.assert_called_once()
+        fetch.assert_called_once_with(force=True)
         self.m.report.log_debug_action.assert_not_called()
 
     def test_dust_amount_remaining(self):
@@ -1966,7 +1985,7 @@ class OrderTest(WebMockTestCase):
                         }
                 order.fetch()
 
-                self.m.ccxt.fetch_order.assert_called_once()
+                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
                 fetch_mouvements.assert_called_once()
                 self.assertEqual("foo", order.status)
                 self.assertEqual("timestamp", order.timestamp)
@@ -1982,7 +2001,7 @@ class OrderTest(WebMockTestCase):
                 fetch_mouvements.assert_not_called()
 
                 order.fetch(force=True)
-                self.m.ccxt.fetch_order.assert_called_once()
+                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
                 fetch_mouvements.assert_called_once()
 
                 self.m.ccxt.fetch_order.reset_mock()
@@ -1990,7 +2009,7 @@ class OrderTest(WebMockTestCase):
 
                 time_mock.return_value = time + 19
                 order.fetch()
-                self.m.ccxt.fetch_order.assert_called_once()
+                self.m.ccxt.fetch_order.assert_called_once_with(45, symbol="ETH")
                 fetch_mouvements.assert_called_once()
                 self.m.report.log_debug_action.assert_not_called()
 
@@ -2586,6 +2605,119 @@ class ReportStoreTest(WebMockTestCase):
 
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class HelperTest(WebMockTestCase):
+    def test_make_order(self):
+        self.m.get_ticker.return_value = {
+                "inverted": False,
+                "average": D("0.1"),
+                "bid": D("0.09"),
+                "ask": D("0.11"),
+                }
+
+        with self.subTest(description="nominal case"):
+            helper.make_order(self.m, 10, "ETH")
+
+            self.m.report.log_stage.assert_has_calls([
+                mock.call("make_order_begin"),
+                mock.call("make_order_end"),
+                ])
+            self.m.balances.fetch_balances.assert_has_calls([
+                mock.call(tag="make_order_begin"),
+                mock.call(tag="make_order_end"),
+                ])
+            self.m.trades.all.append.assert_called_once()
+            trade = self.m.trades.all.append.mock_calls[0][1][0]
+            self.assertEqual(False, trade.orders[0].close_if_possible)
+            self.assertEqual(0, trade.value_from)
+            self.assertEqual("ETH", trade.currency)
+            self.assertEqual("BTC", trade.base_currency)
+            self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average")
+            self.m.trades.run_orders.assert_called_once_with()
+            self.m.follow_orders.assert_called_once_with()
+
+            order = trade.orders[0]
+            self.assertEqual(D("0.10"), order.rate)
+
+            self.m.reset_mock()
+            with self.subTest(compute_value="default"):
+                helper.make_order(self.m, 10, "ETH", action="dispose",
+                        compute_value="ask")
+
+                trade = self.m.trades.all.append.mock_calls[0][1][0]
+                order = trade.orders[0]
+                self.assertEqual(D("0.11"), order.rate)
+
+        self.m.reset_mock()
+        with self.subTest(follow=False):
+            result = helper.make_order(self.m, 10, "ETH", follow=False)
+
+            self.m.report.log_stage.assert_has_calls([
+                mock.call("make_order_begin"),
+                mock.call("make_order_end_not_followed"),
+                ])
+            self.m.balances.fetch_balances.assert_called_once_with(tag="make_order_begin")
+
+            self.m.trades.all.append.assert_called_once()
+            trade = self.m.trades.all.append.mock_calls[0][1][0]
+            self.assertEqual(0, trade.value_from)
+            self.assertEqual("ETH", trade.currency)
+            self.assertEqual("BTC", trade.base_currency)
+            self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average")
+            self.m.trades.run_orders.assert_called_once_with()
+            self.m.follow_orders.assert_not_called()
+            self.assertEqual(trade.orders[0], result)
+
+        self.m.reset_mock()
+        with self.subTest(base_currency="USDT"):
+            helper.make_order(self.m, 1, "BTC", base_currency="USDT")
+
+            trade = self.m.trades.all.append.mock_calls[0][1][0]
+            self.assertEqual("BTC", trade.currency)
+            self.assertEqual("USDT", trade.base_currency)
+
+        self.m.reset_mock()
+        with self.subTest(close_if_possible=True):
+            helper.make_order(self.m, 10, "ETH", close_if_possible=True)
+
+            trade = self.m.trades.all.append.mock_calls[0][1][0]
+            self.assertEqual(True, trade.orders[0].close_if_possible)
+
+        self.m.reset_mock()
+        with self.subTest(action="dispose"):
+            helper.make_order(self.m, 10, "ETH", action="dispose")
+
+            trade = self.m.trades.all.append.mock_calls[0][1][0]
+            self.assertEqual(0, trade.value_to)
+            self.assertEqual(1, trade.value_from.value)
+            self.assertEqual("ETH", trade.currency)
+            self.assertEqual("BTC", trade.base_currency)
+
+            self.m.reset_mock()
+            with self.subTest(compute_value="default"):
+                helper.make_order(self.m, 10, "ETH", action="dispose",
+                        compute_value="bid")
+
+                trade = self.m.trades.all.append.mock_calls[0][1][0]
+                self.assertEqual(D("0.9"), trade.value_from.value)
+
+    def test_user_market(self):
+        with mock.patch("helper.main_fetch_markets") as main_fetch_markets,\
+                mock.patch("helper.main_parse_config") as main_parse_config:
+            with self.subTest(debug=False):
+                main_parse_config.return_value = ["pg_config", "report_path"]
+                main_fetch_markets.return_value = [({"key": "market_config"},)]
+                m = helper.get_user_market("config_path.ini", 1)
+
+                self.assertIsInstance(m, market.Market)
+                self.assertFalse(m.debug)
+
+            with self.subTest(debug=True):
+                main_parse_config.return_value = ["pg_config", "report_path"]
+                main_fetch_markets.return_value = [({"key": "market_config"},)]
+                m = helper.get_user_market("config_path.ini", 1, debug=True)
+
+                self.assertIsInstance(m, market.Market)
+                self.assertTrue(m.debug)
+
     def test_main_store_report(self):
         file_open = mock.mock_open()
         with self.subTest(file=None), mock.patch("__main__.open", file_open):