]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - tests/test_ccxt_wrapper.py
Move tests to separate files
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / tests / test_ccxt_wrapper.py
diff --git a/tests/test_ccxt_wrapper.py b/tests/test_ccxt_wrapper.py
new file mode 100644 (file)
index 0000000..d32469a
--- /dev/null
@@ -0,0 +1,477 @@
+from .helper import limits, unittest, mock, D
+import requests_mock
+import market
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class poloniexETest(unittest.TestCase):
+    def setUp(self):
+        super().setUp()
+        self.wm = requests_mock.Mocker()
+        self.wm.start()
+
+        self.s = market.ccxt.poloniexE()
+
+    def tearDown(self):
+        self.wm.stop()
+        super().tearDown()
+
+    def test__init(self):
+        with self.subTest("Nominal case"), \
+                mock.patch("market.ccxt.poloniexE.session") as session:
+            session.request.return_value = "response"
+            ccxt = market.ccxt.poloniexE()
+            ccxt._market = mock.Mock
+            ccxt._market.report = mock.Mock()
+
+            ccxt.session.request("GET", "URL", data="data",
+                    headers="headers")
+            ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
+                    'headers', 'response')
+
+        with self.subTest("Raising"),\
+                mock.patch("market.ccxt.poloniexE.session") as session:
+            session.request.side_effect = market.ccxt.RequestException("Boo")
+
+            ccxt = market.ccxt.poloniexE()
+            ccxt._market = mock.Mock
+            ccxt._market.report = mock.Mock()
+
+            with self.assertRaises(market.ccxt.RequestException, msg="Boo") as cm:
+                ccxt.session.request("GET", "URL", data="data",
+                        headers="headers")
+            ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
+                    'headers', cm.exception)
+
+
+    def test_nanoseconds(self):
+        with mock.patch.object(market.ccxt.time, "time") as time:
+            time.return_value = 123456.7890123456
+            self.assertEqual(123456789012345, self.s.nanoseconds())
+
+    def test_nonce(self):
+        with mock.patch.object(market.ccxt.time, "time") as time:
+            time.return_value = 123456.7890123456
+            self.assertEqual(123456789012345, self.s.nonce())
+
+    def test_request(self):
+        with mock.patch.object(market.ccxt.poloniex, "request") as request,\
+                mock.patch("market.ccxt.retry_call") as retry_call:
+            with self.subTest(wrapped=True):
+                with self.subTest(desc="public"):
+                    self.s.request("foo")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["foo"],
+                            fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private GET"):
+                    self.s.request("foo", api="private")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["foo"],
+                            fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private POST regexp"):
+                    self.s.request("returnFoo", api="private", method="POST")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["returnFoo"],
+                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private POST non-regexp"):
+                    self.s.request("getMarginPosition", api="private", method="POST")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["getMarginPosition"],
+                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
+                    request.assert_not_called()
+            retry_call.reset_mock()
+            request.reset_mock()
+            with self.subTest(wrapped=False):
+                with self.subTest(desc="private POST non-matching regexp"):
+                    self.s.request("marginBuy", api="private", method="POST")
+                    request.assert_called_with("marginBuy",
+                            api="private", method="POST", params={},
+                            headers=None, body=None)
+                    retry_call.assert_not_called()
+
+                with self.subTest(desc="private POST non-matching non-regexp"):
+                    self.s.request("closeMarginPositionOther", api="private", method="POST")
+                    request.assert_called_with("closeMarginPositionOther",
+                            api="private", method="POST", params={},
+                            headers=None, body=None)
+                    retry_call.assert_not_called()
+
+    def test_order_precision(self):
+        self.assertEqual(8, self.s.order_precision("FOO"))
+
+    def test_transfer_balance(self):
+        with self.subTest(success=True),\
+                mock.patch.object(self.s, "privatePostTransferBalance") as t:
+            t.return_value = { "success": 1 }
+            result = self.s.transfer_balance("FOO", 12, "exchange", "margin")
+            t.assert_called_once_with({
+                "currency": "FOO",
+                "amount": 12,
+                "fromAccount": "exchange",
+                "toAccount": "margin",
+                "confirmed": 1
+                })
+            self.assertTrue(result)
+
+        with self.subTest(success=False),\
+                mock.patch.object(self.s, "privatePostTransferBalance") as t:
+            t.return_value = { "success": 0 }
+            self.assertFalse(self.s.transfer_balance("FOO", 12, "exchange", "margin"))
+
+    def test_close_margin_position(self):
+        with mock.patch.object(self.s, "privatePostCloseMarginPosition") as c:
+            self.s.close_margin_position("FOO", "BAR")
+            c.assert_called_with({"currencyPair": "BAR_FOO"})
+
+    def test_tradable_balances(self):
+        with mock.patch.object(self.s, "privatePostReturnTradableBalances") as r:
+            r.return_value = {
+                    "FOO": { "exchange": "12.1234", "margin": "0.0123" },
+                    "BAR": { "exchange": "1", "margin": "0" },
+                    }
+            balances = self.s.tradable_balances()
+            self.assertEqual(["FOO", "BAR"], list(balances.keys()))
+            self.assertEqual(["exchange", "margin"], list(balances["FOO"].keys()))
+            self.assertEqual(D("12.1234"), balances["FOO"]["exchange"])
+            self.assertEqual(["exchange", "margin"], list(balances["BAR"].keys()))
+
+    def test_margin_summary(self):
+        with mock.patch.object(self.s, "privatePostReturnMarginAccountSummary") as r:
+            r.return_value = {
+                    "currentMargin": "1.49680968",
+                    "lendingFees": "0.0000001",
+                    "pl": "0.00008254",
+                    "totalBorrowedValue": "0.00673602",
+                    "totalValue": "0.01000000",
+                    "netValue": "0.01008254",
+                    }
+            expected = {
+                    'current_margin': D('1.49680968'),
+                    'gains': D('0.00008254'),
+                    'lending_fees': D('0.0000001'),
+                    'total': D('0.01000000'),
+                    'total_borrowed': D('0.00673602')
+                    }
+            self.assertEqual(expected, self.s.margin_summary())
+
+    def test_create_order(self):
+        with mock.patch.object(self.s, "create_exchange_order") as exchange,\
+                mock.patch.object(self.s, "create_margin_order") as margin:
+            with self.subTest(account="unspecified"):
+                self.s.create_order("symbol", "type", "side", "amount", price="price", lending_rate="lending_rate", params="params")
+                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
+                margin.assert_not_called()
+                exchange.reset_mock()
+                margin.reset_mock()
+
+            with self.subTest(account="exchange"):
+                self.s.create_order("symbol", "type", "side", "amount", account="exchange", price="price", lending_rate="lending_rate", params="params")
+                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
+                margin.assert_not_called()
+                exchange.reset_mock()
+                margin.reset_mock()
+
+            with self.subTest(account="margin"):
+                self.s.create_order("symbol", "type", "side", "amount", account="margin", price="price", lending_rate="lending_rate", params="params")
+                margin.assert_called_once_with("symbol", "type", "side", "amount", lending_rate="lending_rate", price="price", params="params")
+                exchange.assert_not_called()
+                exchange.reset_mock()
+                margin.reset_mock()
+
+            with self.subTest(account="unknown"), self.assertRaises(NotImplementedError):
+                self.s.create_order("symbol", "type", "side", "amount", account="unknown")
+
+    def test_parse_ticker(self):
+        ticker = {
+                "high24hr": "12",
+                "low24hr": "10",
+                "highestBid": "10.5",
+                "lowestAsk": "11.5",
+                "last": "11",
+                "percentChange": "0.1",
+                "quoteVolume": "10",
+                "baseVolume": "20"
+                }
+        market = {
+                "symbol": "BTC/ETC"
+                }
+        with mock.patch.object(self.s, "milliseconds") as ms:
+            ms.return_value = 1520292715123
+            result = self.s.parse_ticker(ticker, market)
+
+            expected = {
+                    "symbol": "BTC/ETC",
+                    "timestamp": 1520292715123,
+                    "datetime": "2018-03-05T23:31:55.123Z",
+                    "high": D("12"),
+                    "low": D("10"),
+                    "bid": D("10.5"),
+                    "ask": D("11.5"),
+                    "vwap": None,
+                    "open": None,
+                    "close": None,
+                    "first": None,
+                    "last": D("11"),
+                    "change": D("0.1"),
+                    "percentage": None,
+                    "average": None,
+                    "baseVolume": D("10"),
+                    "quoteVolume": D("20"),
+                    "info": ticker
+                    }
+            self.assertEqual(expected, result)
+
+    def test_fetch_margin_balance(self):
+        with mock.patch.object(self.s, "privatePostGetMarginPosition") as get_margin_position:
+            get_margin_position.return_value = {
+                    "BTC_DASH": {
+                        "amount": "-0.1",
+                        "basePrice": "0.06818560",
+                        "lendingFees": "0.00000001",
+                        "liquidationPrice": "0.15107132",
+                        "pl": "-0.00000371",
+                        "total": "0.00681856",
+                        "type": "short"
+                        },
+                    "BTC_ETC": {
+                        "amount": "-0.6",
+                        "basePrice": "0.1",
+                        "lendingFees": "0.00000001",
+                        "liquidationPrice": "0.6",
+                        "pl": "0.00000371",
+                        "total": "0.06",
+                        "type": "short"
+                        },
+                    "BTC_ETH": {
+                        "amount": "0",
+                        "basePrice": "0",
+                        "lendingFees": "0",
+                        "liquidationPrice": "-1",
+                        "pl": "0",
+                        "total": "0",
+                        "type": "none"
+                        }
+                    }
+            balances = self.s.fetch_margin_balance()
+            self.assertEqual(2, len(balances))
+            expected = {
+                "DASH": {
+                    "amount": D("-0.1"),
+                    "borrowedPrice": D("0.06818560"),
+                    "lendingFees": D("1E-8"),
+                    "pl": D("-0.00000371"),
+                    "liquidationPrice": D("0.15107132"),
+                    "type": "short",
+                    "total": D("0.00681856"),
+                    "baseCurrency": "BTC"
+                    },
+                "ETC": {
+                    "amount": D("-0.6"),
+                    "borrowedPrice": D("0.1"),
+                    "lendingFees": D("1E-8"),
+                    "pl": D("0.00000371"),
+                    "liquidationPrice": D("0.6"),
+                    "type": "short",
+                    "total": D("0.06"),
+                    "baseCurrency": "BTC"
+                    }
+                }
+            self.assertEqual(expected, balances)
+
+    def test_sum(self):
+        self.assertEqual(D("1.1"), self.s.sum(D("1"), D("0.1")))
+
+    def test_fetch_balance(self):
+        with mock.patch.object(self.s, "load_markets") as load_markets,\
+                mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balances,\
+                mock.patch.object(self.s, "common_currency_code") as ccc:
+            ccc.side_effect = ["ETH", "BTC", "DASH"]
+            balances.return_value = {
+                    "ETH": {
+                        "available": "10",
+                        "onOrders": "1",
+                        },
+                    "BTC": {
+                        "available": "1",
+                        "onOrders": "0",
+                        },
+                    "DASH": {
+                        "available": "0",
+                        "onOrders": "3"
+                        }
+                    }
+
+            expected = {
+                    "info": {
+                        "ETH": {"available": "10", "onOrders": "1"},
+                        "BTC": {"available": "1", "onOrders": "0"},
+                        "DASH": {"available": "0", "onOrders": "3"}
+                        },
+                    "ETH": {"free": D("10"), "used": D("1"), "total": D("11")},
+                    "BTC": {"free": D("1"), "used": D("0"), "total": D("1")},
+                    "DASH": {"free": D("0"), "used": D("3"), "total": D("3")},
+                    "free": {"ETH": D("10"), "BTC": D("1"), "DASH": D("0")},
+                    "used": {"ETH": D("1"), "BTC": D("0"), "DASH": D("3")},
+                    "total": {"ETH": D("11"), "BTC": D("1"), "DASH": D("3")}
+                    }
+            result = self.s.fetch_balance()
+            load_markets.assert_called_once()
+            self.assertEqual(expected, result)
+
+    def test_fetch_balance_per_type(self):
+        with mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balances:
+            balances.return_value = {
+                "exchange": {
+                    "BLK": "159.83673869",
+                    "BTC": "0.00005959",
+                    "USDT": "0.00002625",
+                    "XMR": "0.18719303"
+                    },
+                "margin": {
+                    "BTC": "0.03019227"
+                    }
+                }
+            expected = {
+                    "info": {
+                        "exchange": {
+                            "BLK": "159.83673869",
+                            "BTC": "0.00005959",
+                            "USDT": "0.00002625",
+                            "XMR": "0.18719303"
+                            },
+                        "margin": {
+                            "BTC": "0.03019227"
+                            }
+                        },
+                    "exchange": {
+                        "BLK": D("159.83673869"),
+                        "BTC": D("0.00005959"),
+                        "USDT": D("0.00002625"),
+                        "XMR": D("0.18719303")
+                        },
+                    "margin": {"BTC": D("0.03019227")},
+                    "BLK": {"exchange": D("159.83673869")},
+                    "BTC": {"exchange": D("0.00005959"), "margin": D("0.03019227")},
+                    "USDT": {"exchange": D("0.00002625")},
+                    "XMR": {"exchange": D("0.18719303")}
+                    } 
+            result = self.s.fetch_balance_per_type()
+            self.assertEqual(expected, result)
+
+    def test_fetch_all_balances(self):
+        import json
+        with mock.patch.object(self.s, "load_markets") as load_markets,\
+                mock.patch.object(self.s, "privatePostGetMarginPosition") as margin_balance,\
+                mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balance,\
+                mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balance_per_type:
+
+            with open("test_samples/poloniexETest.test_fetch_all_balances.1.json") as f:
+                balance.return_value = json.load(f)
+            with open("test_samples/poloniexETest.test_fetch_all_balances.2.json") as f:
+                margin_balance.return_value = json.load(f)
+            with open("test_samples/poloniexETest.test_fetch_all_balances.3.json") as f:
+                balance_per_type.return_value = json.load(f)
+
+            result = self.s.fetch_all_balances()
+            expected_doge = {
+                    "total": D("-12779.79821852"),
+                    "exchange_used": D("0E-8"),
+                    "exchange_total": D("0E-8"),
+                    "exchange_free": D("0E-8"),
+                    "margin_available": 0,
+                    "margin_in_position": 0,
+                    "margin_borrowed": D("12779.79821852"),
+                    "margin_total": D("-12779.79821852"),
+                    "margin_pending_gain": 0,
+                    "margin_lending_fees": D("-9E-8"),
+                    "margin_pending_base_gain": D("0.00024059"),
+                    "margin_position_type": "short",
+                    "margin_liquidation_price": D("0.00000246"),
+                    "margin_borrowed_base_price": D("0.00599149"),
+                    "margin_borrowed_base_currency": "BTC"
+                    } 
+            expected_btc = {"total": D("0.05432165"),
+                    "exchange_used": D("0E-8"),
+                    "exchange_total": D("0.00005959"),
+                    "exchange_free": D("0.00005959"),
+                    "margin_available": D("0.03019227"),
+                    "margin_in_position": D("0.02406979"),
+                    "margin_borrowed": 0,
+                    "margin_total": D("0.05426206"),
+                    "margin_pending_gain": D("0.00093955"),
+                    "margin_lending_fees": 0,
+                    "margin_pending_base_gain": 0,
+                    "margin_position_type": None,
+                    "margin_liquidation_price": 0,
+                    "margin_borrowed_base_price": 0,
+                    "margin_borrowed_base_currency": None
+                    }
+            expected_xmr = {"total": D("0.18719303"),
+                    "exchange_used": D("0E-8"),
+                    "exchange_total": D("0.18719303"),
+                    "exchange_free": D("0.18719303"),
+                    "margin_available": 0,
+                    "margin_in_position": 0,
+                    "margin_borrowed": 0,
+                    "margin_total": 0,
+                    "margin_pending_gain": 0,
+                    "margin_lending_fees": 0,
+                    "margin_pending_base_gain": 0,
+                    "margin_position_type": None,
+                    "margin_liquidation_price": 0,
+                    "margin_borrowed_base_price": 0,
+                    "margin_borrowed_base_currency": None
+                    } 
+            self.assertEqual(expected_xmr, result["XMR"])
+            self.assertEqual(expected_doge, result["DOGE"])
+            self.assertEqual(expected_btc, result["BTC"])
+
+    def test_create_margin_order(self):
+        with self.assertRaises(market.ExchangeError):
+            self.s.create_margin_order("FOO", "market", "buy", "10")
+
+        with mock.patch.object(self.s, "load_markets") as load_markets,\
+                mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
+                mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
+                mock.patch.object(self.s, "market") as market_mock,\
+                mock.patch.object(self.s, "price_to_precision") as ptp,\
+                mock.patch.object(self.s, "amount_to_precision") as atp:
+
+            margin_buy.return_value = {
+                    "orderNumber": 123
+                    }
+            margin_sell.return_value = {
+                    "orderNumber": 456
+                    }
+            market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
+            ptp.return_value = D("0.1")
+            atp.return_value = D("12")
+
+            order = self.s.create_margin_order("BTC_ETC", "margin", "buy", "12", price="0.1")
+            self.assertEqual(123, order["id"])
+            margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
+            margin_sell.assert_not_called()
+            margin_buy.reset_mock()
+            margin_sell.reset_mock()
+
+            order = self.s.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate="0.01", price="0.1")
+            self.assertEqual(456, order["id"])
+            margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
+            margin_buy.assert_not_called()
+
+    def test_create_exchange_order(self):
+        with mock.patch.object(market.ccxt.poloniex, "create_order") as create_order:
+            self.s.create_order("symbol", "type", "side", "amount", price="price", params="params")
+
+            create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
+
+