]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - test.py
Add mouvement representation
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
diff --git a/test.py b/test.py
index 890381541368f5d683d942584152e0dfbd5324f0..9fd058a0ad6d59c80798466d4cbf4ddace1ab6eb 100644 (file)
--- a/test.py
+++ b/test.py
@@ -32,7 +32,7 @@ class WebMockTestCase(unittest.TestCase):
                 mock.patch.multiple(portfolio.TradeStore,
                     all=[],
                     debug=False),
-                mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
+                mock.patch.multiple(portfolio.Portfolio, last_date=None, data=None, liquidities={}),
                 mock.patch.multiple(portfolio.Computation,
                     computations=portfolio.Computation.computations),
                 mock.patch.multiple(helper,
@@ -102,7 +102,8 @@ class PortfolioTest(WebMockTestCase):
                 'SC':   (D("0.0623"), "long"),
                 'ZEC':  (D("0.3701"), "long"),
                 }
-        self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
+        date = portfolio.datetime(2018, 1, 8)
+        self.assertDictEqual(expected, liquidities["high"][date])
 
         expected = {
                 'BTC':  (D("1.1102e-16"), "long"),
@@ -117,13 +118,17 @@ class PortfolioTest(WebMockTestCase):
                 'VIA':  (D("0.1"), "long"),
                 'XCP':  (D("0.1"), "long"),
                 }
-        self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
+        self.assertDictEqual(expected, liquidities["medium"][date])
+        self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)
 
         # It doesn't refetch the data when available
         portfolio.Portfolio.parse_cryptoportfolio()
 
         self.assertEqual(1, self.wm.call_count)
 
+        portfolio.Portfolio.parse_cryptoportfolio(refetch=True)
+        self.assertEqual(2, self.wm.call_count)
+
     def test_repartition(self):
         expected_medium = {
                 'BTC':   (D("1.1102e-16"), "long"),
@@ -151,6 +156,48 @@ class PortfolioTest(WebMockTestCase):
         self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
         self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))
 
+        self.assertEqual(1, self.wm.call_count)
+
+        portfolio.Portfolio.repartition()
+        self.assertEqual(1, self.wm.call_count)
+
+        portfolio.Portfolio.repartition(refetch=True)
+        self.assertEqual(2, self.wm.call_count)
+
+    @mock.patch.object(portfolio.time, "sleep")
+    @mock.patch.object(portfolio.Portfolio, "repartition")
+    def test_wait_for_recent(self, repartition, sleep):
+        self.call_count = 0
+        def _repartition(refetch):
+            self.assertTrue(refetch)
+            self.call_count += 1
+            portfolio.Portfolio.last_date = portfolio.datetime.now()\
+                - portfolio.timedelta(10)\
+                + portfolio.timedelta(self.call_count)
+        repartition.side_effect = _repartition
+
+        portfolio.Portfolio.wait_for_recent()
+        sleep.assert_called_with(30)
+        self.assertEqual(6, sleep.call_count)
+        self.assertEqual(7, repartition.call_count)
+
+        sleep.reset_mock()
+        repartition.reset_mock()
+        portfolio.Portfolio.last_date = None
+        self.call_count = 0
+        portfolio.Portfolio.wait_for_recent(delta=15)
+        sleep.assert_not_called()
+        self.assertEqual(1, repartition.call_count)
+
+        sleep.reset_mock()
+        repartition.reset_mock()
+        portfolio.Portfolio.last_date = None
+        self.call_count = 0
+        portfolio.Portfolio.wait_for_recent(delta=1)
+        sleep.assert_called_with(30)
+        self.assertEqual(9, sleep.call_count)
+        self.assertEqual(10, repartition.call_count)
+
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class AmountTest(WebMockTestCase):
     def test_values(self):
@@ -220,6 +267,8 @@ class AmountTest(WebMockTestCase):
         amount4 = portfolio.Amount("ETH", 0.0)
         self.assertEqual(amount1, amount1 + amount4)
 
+        self.assertEqual(amount1, amount1 + 0)
+
     def test__radd(self):
         amount = portfolio.Amount("XVG", "12.9")
 
@@ -241,6 +290,13 @@ class AmountTest(WebMockTestCase):
         amount4 = portfolio.Amount("ETH", 0.0)
         self.assertEqual(amount1, amount1 - amount4)
 
+    def test__rsub(self):
+        amount = portfolio.Amount("ETH", "1.6")
+        with self.assertRaises(Exception):
+            3 - amount
+
+        self.assertEqual(portfolio.Amount("ETH", "-1.6"), -amount)
+
     def test__mul(self):
         amount = portfolio.Amount("XEM", 11)
 
@@ -724,7 +780,8 @@ class HelperTest(WebMockTestCase):
                 portfolio.TradeStore.all = [trade1, trade2, trade3]
                 balance1 = portfolio.Balance("BTC", { "margin_free": "0" })
                 balance2 = portfolio.Balance("USDT", { "margin_free": "100" })
-                portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2}
+                balance3 = portfolio.Balance("ETC", { "margin_free": "10" })
+                portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
 
                 market = mock.Mock()
 
@@ -736,6 +793,7 @@ class HelperTest(WebMockTestCase):
                 else:
                     market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
                     market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
+                    market.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange")
 
     @mock.patch.object(helper, "prepare_trades")
     @mock.patch.object(portfolio.TradeStore, "prepare_orders")
@@ -757,9 +815,165 @@ class HelperTest(WebMockTestCase):
                 }
         helper.print_orders(market)
         prepare_trades.assert_called_with(market, base_currency="BTC",
-                compute_value="average")
+                compute_value="average", debug=True)
+        prepare_orders.assert_called_with(compute_value="average")
+        print_all_with_order.assert_called()
+        self.assertRegex(stdout_mock.getvalue(), "Balance")
+
+    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
+    @mock.patch.object(portfolio.BalanceStore, "in_currency")
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_print_balances(self, stdout_mock, in_currency, fetch_balances):
+        market = mock.Mock()
+        portfolio.BalanceStore.all = {
+                "BTC": portfolio.Balance("BTC", {
+                    "total": "0.65",
+                    "exchange_total":"0.65",
+                    "exchange_free": "0.35",
+                    "exchange_used": "0.30"}),
+                "ETH": portfolio.Balance("ETH", {
+                    "total": 3,
+                    "exchange_total": 3,
+                    "exchange_free": 3,
+                    "exchange_used": 0}),
+                }
+        in_currency.return_value = {
+                "BTC": portfolio.Amount("BTC", "0.65"),
+                "ETH": portfolio.Amount("BTC", "0.3"),
+        }
+        helper.print_balances(market)
+        fetch_balances.assert_called_with(market)
+        self.assertRegex(stdout_mock.getvalue(), "Balance")
+        self.assertRegex(stdout_mock.getvalue(), "0.95000000 BTC")
+
+    @mock.patch.object(helper, "prepare_trades")
+    @mock.patch.object(helper, "follow_orders")
+    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
+    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
+    @mock.patch.object(portfolio.TradeStore, "run_orders")
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_process_sell_needed__1_sell(self, stdout_mock, run_orders,
+            print_all_with_order, prepare_orders, follow_orders,
+            prepare_trades):
+        market = mock.Mock()
+        portfolio.BalanceStore.all = {
+                "BTC": portfolio.Balance("BTC", {
+                    "total": "0.65",
+                    "exchange_total":"0.65",
+                    "exchange_free": "0.35",
+                    "exchange_used": "0.30"}),
+                "ETH": portfolio.Balance("ETH", {
+                    "total": 3,
+                    "exchange_total": 3,
+                    "exchange_free": 3,
+                    "exchange_used": 0}),
+                }
+        helper.process_sell_needed__1_sell(market)
+        prepare_trades.assert_called_with(market, base_currency="BTC",
+                liquidity="medium", debug=False)
+        prepare_orders.assert_called_with(compute_value="average",
+                only="dispose")
+        print_all_with_order.assert_called()
+        run_orders.assert_called()
+        follow_orders.assert_called()
+        self.assertRegex(stdout_mock.getvalue(), "Balance")
+
+    @mock.patch.object(helper, "update_trades")
+    @mock.patch.object(helper, "follow_orders")
+    @mock.patch.object(helper, "move_balances")
+    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
+    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
+    @mock.patch.object(portfolio.TradeStore, "run_orders")
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_process_sell_needed__2_buy(self, stdout_mock, run_orders,
+            print_all_with_order, prepare_orders, move_balances,
+            follow_orders, update_trades):
+        market = mock.Mock()
+        portfolio.BalanceStore.all = {
+                "BTC": portfolio.Balance("BTC", {
+                    "total": "0.65",
+                    "exchange_total":"0.65",
+                    "exchange_free": "0.35",
+                    "exchange_used": "0.30"}),
+                "ETH": portfolio.Balance("ETH", {
+                    "total": 3,
+                    "exchange_total": 3,
+                    "exchange_free": 3,
+                    "exchange_used": 0}),
+                }
+        helper.process_sell_needed__2_buy(market)
+        update_trades.assert_called_with(market, base_currency="BTC",
+                debug=False, liquidity="medium", only="acquire")
+        prepare_orders.assert_called_with(compute_value="average",
+                only="acquire")
+        print_all_with_order.assert_called()
+        move_balances.assert_called_with(market, debug=False)
+        run_orders.assert_called()
+        follow_orders.assert_called()
+        self.assertRegex(stdout_mock.getvalue(), "Balance")
+
+    @mock.patch.object(helper, "prepare_trades_to_sell_all")
+    @mock.patch.object(helper, "follow_orders")
+    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
+    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
+    @mock.patch.object(portfolio.TradeStore, "run_orders")
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_process_sell_all__1_sell(self, stdout_mock, run_orders,
+            print_all_with_order, prepare_orders, follow_orders,
+            prepare_trades_to_sell_all):
+        market = mock.Mock()
+        portfolio.BalanceStore.all = {
+                "BTC": portfolio.Balance("BTC", {
+                    "total": "0.65",
+                    "exchange_total":"0.65",
+                    "exchange_free": "0.35",
+                    "exchange_used": "0.30"}),
+                "ETH": portfolio.Balance("ETH", {
+                    "total": 3,
+                    "exchange_total": 3,
+                    "exchange_free": 3,
+                    "exchange_used": 0}),
+                }
+        helper.process_sell_all__1_all_sell(market)
+        prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
+                debug=False)
+        prepare_orders.assert_called_with(compute_value="average")
+        print_all_with_order.assert_called()
+        run_orders.assert_called()
+        follow_orders.assert_called()
+        self.assertRegex(stdout_mock.getvalue(), "Balance")
+
+    @mock.patch.object(helper, "prepare_trades")
+    @mock.patch.object(helper, "follow_orders")
+    @mock.patch.object(helper, "move_balances")
+    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
+    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
+    @mock.patch.object(portfolio.TradeStore, "run_orders")
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_process_sell_all__2_all_buy(self, stdout_mock, run_orders,
+            print_all_with_order, prepare_orders, move_balances,
+            follow_orders, prepare_trades):
+        market = mock.Mock()
+        portfolio.BalanceStore.all = {
+                "BTC": portfolio.Balance("BTC", {
+                    "total": "0.65",
+                    "exchange_total":"0.65",
+                    "exchange_free": "0.35",
+                    "exchange_used": "0.30"}),
+                "ETH": portfolio.Balance("ETH", {
+                    "total": 3,
+                    "exchange_total": 3,
+                    "exchange_free": 3,
+                    "exchange_used": 0}),
+                }
+        helper.process_sell_all__2_all_buy(market)
+        prepare_trades.assert_called_with(market, base_currency="BTC",
+                liquidity="medium", debug=False)
         prepare_orders.assert_called_with(compute_value="average")
         print_all_with_order.assert_called()
+        move_balances.assert_called_with(market, debug=False)
+        run_orders.assert_called()
+        follow_orders.assert_called()
         self.assertRegex(stdout_mock.getvalue(), "Balance")
 
 
@@ -1033,6 +1247,7 @@ class BalanceStoreTest(WebMockTestCase):
                 }
 
         amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
+        repartition.assert_called_with(liquidity="medium")
         self.assertIn("XEM", portfolio.BalanceStore.currencies())
         self.assertEqual(D("2.6"), amounts["BTC"].value)
         self.assertEqual(D("7.5"), amounts["XEM"].value)
@@ -1421,6 +1636,16 @@ class TradeTest(WebMockTestCase):
         order_mock2 = mock.Mock()
         order_mock2.__repr__ = mock.Mock()
         order_mock2.__repr__.return_value = "Mock 2"
+        order_mock1.mouvements = []
+        mouvement_mock1 = mock.Mock()
+        mouvement_mock1.__repr__ = mock.Mock()
+        mouvement_mock1.__repr__.return_value = "Mouvement 1"
+        mouvement_mock2 = mock.Mock()
+        mouvement_mock2.__repr__ = mock.Mock()
+        mouvement_mock2.__repr__.return_value = "Mouvement 2"
+        order_mock2.mouvements = [
+                mouvement_mock1, mouvement_mock2
+                ]
         trade.orders.append(order_mock1)
         trade.orders.append(order_mock2)
 
@@ -1430,6 +1655,8 @@ class TradeTest(WebMockTestCase):
         self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0])
         self.assertEqual("\tMock 1", out[1])
         self.assertEqual("\tMock 2", out[2])
+        self.assertEqual("\t\tMouvement 1", out[3])
+        self.assertEqual("\t\tMouvement 2", out[4])
 
     def test__repr(self):
         value_from = portfolio.Amount("BTC", "0.5")
@@ -1549,12 +1776,12 @@ class OrderTest(WebMockTestCase):
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                 D("0.1"), "BTC", "long", "market", "trade")
         order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
-            "id": 42, "type": "buy", "fee": "0.0015",
+            "tradeID": 42, "type": "buy", "fee": "0.0015",
             "date": "2017-12-30 12:00:12", "rate": "0.1",
             "amount": "3", "total": "0.3"
             }))
         order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
-            "id": 43, "type": "buy", "fee": "0.0015",
+            "tradeID": 43, "type": "buy", "fee": "0.0015",
             "date": "2017-12-30 13:00:12", "rate": "0.2",
             "amount": "2", "total": "0.4"
             }))
@@ -1569,12 +1796,12 @@ class OrderTest(WebMockTestCase):
         market = mock.Mock()
         market.privatePostReturnOrderTrades.return_value = [
                 {
-                    "id": 42, "type": "buy", "fee": "0.0015",
+                    "tradeID": 42, "type": "buy", "fee": "0.0015",
                     "date": "2017-12-30 12:00:12", "rate": "0.1",
                     "amount": "3", "total": "0.3"
                     },
                 {
-                    "id": 43, "type": "buy", "fee": "0.0015",
+                    "tradeID": 43, "type": "buy", "fee": "0.0015",
                     "date": "2017-12-30 13:00:12", "rate": "0.2",
                     "amount": "2", "total": "0.4"
                     }
@@ -1591,6 +1818,12 @@ class OrderTest(WebMockTestCase):
         self.assertEqual(42, order.mouvements[0].id)
         self.assertEqual(43, order.mouvements[1].id)
 
+        market.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
+        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+                D("0.1"), "BTC", "long", market, "trade")
+        order.fetch_mouvements()
+        self.assertEqual(0, len(order.mouvements))
+
     def test_mark_finished_order(self):
         market = mock.Mock()
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
@@ -1770,11 +2003,24 @@ class OrderTest(WebMockTestCase):
             self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order")
             self.assertRegex(stdout_mock.getvalue(), "Exception: bouh")
 
+        market.create_order.reset_mock()
+        with self.subTest(dust_amount_exception=True),\
+                mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
+            order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
+                    D("0.1"), "BTC", "long", market, "trade")
+            market.create_order.side_effect = portfolio.ExchangeNotAvailable
+            order.run()
+            market.create_order.assert_called_once()
+            self.assertEqual(0, len(order.results))
+            self.assertEqual("closed", order.status)
+            mark_finished_order.assert_called_once()
+
+
 @unittest.skipUnless("unit" in limits, "Unit skipped")
 class MouvementTest(WebMockTestCase):
     def test_values(self):
         mouvement = portfolio.Mouvement("ETH", "BTC", {
-            "id": 42, "type": "buy", "fee": "0.0015",
+            "tradeID": 42, "type": "buy", "fee": "0.0015",
             "date": "2017-12-30 12:00:12", "rate": "0.1",
             "amount": "10", "total": "1"
             })
@@ -1788,6 +2034,29 @@ class MouvementTest(WebMockTestCase):
         self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
         self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)
 
+        mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
+        self.assertIsNone(mouvement.date)
+        self.assertIsNone(mouvement.id)
+        self.assertIsNone(mouvement.action)
+        self.assertEqual(-1, mouvement.fee_rate)
+        self.assertEqual(0, mouvement.rate)
+        self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
+        self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)
+
+    def test__repr(self):
+        mouvement = portfolio.Mouvement("ETH", "BTC", {
+            "tradeID": 42, "type": "buy", "fee": "0.0015",
+            "date": "2017-12-30 12:00:12", "rate": "0.1",
+            "amount": "10", "total": "1"
+            })
+        self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement))
+        mouvement = portfolio.Mouvement("ETH", "BTC", {
+            "tradeID": 42, "type": "buy",
+            "date": "garbage", "rate": "0.1",
+            "amount": "10", "total": "1"
+            })
+        self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement))
+
 @unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
 class AcceptanceTest(WebMockTestCase):
     @unittest.expectedFailure
@@ -1939,7 +2208,7 @@ class AcceptanceTest(WebMockTestCase):
         market.fetch_order.return_value = { "status": "closed", "datetime": "2018-01-20 13:40:00" }
         market.privatePostReturnOrderTrades.return_value = [
                 {
-                    "id": 42, "type": "buy", "fee": "0.0015",
+                    "tradeID": 42, "type": "buy", "fee": "0.0015",
                     "date": "2017-12-30 12:00:12", "rate": "0.1",
                     "amount": "10", "total": "1"
                     }