3 from decimal
import Decimal
as D
4 from unittest
import mock
6 class AmountTest(unittest
.TestCase
):
8 amount
= portfolio
.Amount("BTC", "0.65")
9 self
.assertEqual(D("0.65"), amount
.value
)
10 self
.assertEqual("BTC", amount
.currency
)
12 def test_in_currency(self
):
13 amount
= portfolio
.Amount("ETC", 10)
15 self
.assertEqual(amount
, amount
.in_currency("ETC", None))
17 ticker_mock
= unittest
.mock
.Mock()
18 with mock
.patch
.object(portfolio
.Trade
, 'get_ticker', new
=ticker_mock
):
19 ticker_mock
.return_value
= None
21 self
.assertRaises(Exception, amount
.in_currency
, "ETH", None)
23 with mock
.patch
.object(portfolio
.Trade
, 'get_ticker', new
=ticker_mock
):
24 ticker_mock
.return_value
= {
30 converted_amount
= amount
.in_currency("ETH", None)
32 self
.assertEqual(D("3.0"), converted_amount
.value
)
33 self
.assertEqual("ETH", converted_amount
.currency
)
34 self
.assertEqual(amount
, converted_amount
.linked_to
)
35 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
37 converted_amount
= amount
.in_currency("ETH", None, action
="bid", compute_value
="default")
38 self
.assertEqual(D("2"), converted_amount
.value
)
40 converted_amount
= amount
.in_currency("ETH", None, compute_value
="ask")
41 self
.assertEqual(D("4"), converted_amount
.value
)
44 amount
= portfolio
.Amount("SC", -120)
45 self
.assertEqual(120, abs(amount
).value
)
46 self
.assertEqual("SC", abs(amount
).currency
)
48 amount
= portfolio
.Amount("SC", 10)
49 self
.assertEqual(10, abs(amount
).value
)
50 self
.assertEqual("SC", abs(amount
).currency
)
53 amount1
= portfolio
.Amount("XVG", "12.9")
54 amount2
= portfolio
.Amount("XVG", "13.1")
56 self
.assertEqual(26, (amount1
+ amount2
).value
)
57 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
59 amount3
= portfolio
.Amount("ETH", "1.6")
60 with self
.assertRaises(Exception):
63 amount4
= portfolio
.Amount("ETH", 0.0)
64 self
.assertEqual(amount1
, amount1
+ amount4
)
67 amount
= portfolio
.Amount("XVG", "12.9")
69 self
.assertEqual(amount
, 0 + amount
)
70 with self
.assertRaises(Exception):
74 amount1
= portfolio
.Amount("XVG", "13.3")
75 amount2
= portfolio
.Amount("XVG", "13.1")
77 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
78 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
80 amount3
= portfolio
.Amount("ETH", "1.6")
81 with self
.assertRaises(Exception):
84 amount4
= portfolio
.Amount("ETH", 0.0)
85 self
.assertEqual(amount1
, amount1
- amount4
)
88 amount
= portfolio
.Amount("XEM", 11)
90 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
91 self
.assertEqual(D("33"), (amount
* 3).value
)
93 with self
.assertRaises(Exception):
97 amount
= portfolio
.Amount("XEM", 11)
99 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
100 self
.assertEqual(D("33"), (3 * amount
).value
)
102 def test__floordiv(self
):
103 amount
= portfolio
.Amount("XEM", 11)
105 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
106 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
109 amount
= portfolio
.Amount("XEM", 11)
111 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
112 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
115 amount1
= portfolio
.Amount("BTD", 11.3)
116 amount2
= portfolio
.Amount("BTD", 13.1)
118 self
.assertTrue(amount1
< amount2
)
119 self
.assertFalse(amount2
< amount1
)
120 self
.assertFalse(amount1
< amount1
)
122 amount3
= portfolio
.Amount("BTC", 1.6)
123 with self
.assertRaises(Exception):
127 amount1
= portfolio
.Amount("BTD", 11.3)
128 amount2
= portfolio
.Amount("BTD", 13.1)
129 amount3
= portfolio
.Amount("BTD", 11.3)
131 self
.assertFalse(amount1
== amount2
)
132 self
.assertFalse(amount2
== amount1
)
133 self
.assertTrue(amount1
== amount3
)
134 self
.assertFalse(amount2
== 0)
136 amount4
= portfolio
.Amount("BTC", 1.6)
137 with self
.assertRaises(Exception):
140 amount5
= portfolio
.Amount("BTD", 0)
141 self
.assertTrue(amount5
== 0)
144 amount1
= portfolio
.Amount("BTX", 32)
145 self
.assertEqual("32.00000000 BTX", str(amount1
))
147 amount2
= portfolio
.Amount("USDT", 12000)
148 amount1
.linked_to
= amount2
149 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
151 def test__repr(self
):
152 amount1
= portfolio
.Amount("BTX", 32)
153 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
155 amount2
= portfolio
.Amount("USDT", 12000)
156 amount1
.linked_to
= amount2
157 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
159 amount3
= portfolio
.Amount("BTC", 0.1)
160 amount2
.linked_to
= amount3
161 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
163 class PortfolioTest(unittest
.TestCase
):
166 if self
.json_response
is not None:
167 portfolio
.Portfolio
.data
= self
.json_response
170 super(PortfolioTest
, self
).setUp()
172 with open("test_portfolio.json") as example
:
174 self
.json_response
= json
.load(example
)
176 self
.patcher
= mock
.patch
.multiple(portfolio
.Portfolio
, data
=None, liquidities
={})
179 @mock.patch.object(urllib3
, "disable_warnings")
180 @mock.patch.object(urllib3
.poolmanager
.PoolManager
, "request")
181 @mock.patch.object(portfolio
.Portfolio
, "URL", new
="foo://bar")
182 def test_get_cryptoportfolio(self
, request
, disable_warnings
):
183 request
.side_effect
= [
184 type('', (), { "data": '{ "foo": "bar" }
' }),
185 type('', (), { "data": 'System Error' }),
186 Exception("Connection error"),
189 portfolio.Portfolio.get_cryptoportfolio()
190 self.assertIn("foo", portfolio.Portfolio.data)
191 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
192 request.assert_called_with("GET", "foo://bar")
195 portfolio.Portfolio.get_cryptoportfolio()
196 self.assertIsNone(portfolio.Portfolio.data)
197 request.assert_called_with("GET", "foo://bar")
200 portfolio.Portfolio.data = "foo"
201 portfolio.Portfolio.get_cryptoportfolio()
202 request.assert_called_with("GET", "foo://bar")
203 self.assertEqual("foo", portfolio.Portfolio.data)
204 disable_warnings.assert_called_with()
206 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
207 def test_parse_cryptoportfolio(self, mock_get):
208 mock_get.side_effect = self.fill_data
210 portfolio.Portfolio.parse_cryptoportfolio()
212 self.assertListEqual(
214 list(portfolio.Portfolio.liquidities.keys()))
216 liquidities = portfolio.Portfolio.liquidities
217 self.assertEqual(10, len(liquidities["medium"].keys()))
218 self.assertEqual(10, len(liquidities["high"].keys()))
220 expected = {'BTC': 2857, 'DGB': 1015, 'DOGE': 1805, 'SC': 623, 'ZEC': 3701}
221 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
223 expected = {'ETC': 1000, 'FCT': 1000, 'GAS': 1000, 'NAV': 1000, 'OMG': 1000, 'OMNI': 1000, 'PPC': 1000, 'RIC': 1000, 'VIA': 1000, 'XCP': 1000}
224 self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])
226 # It doesn't refetch the data when available
227 portfolio
.Portfolio
.parse_cryptoportfolio()
228 mock_get
.assert_called_once_with()
230 portfolio
.Portfolio
.data
["portfolio_1"]["holding"]["direction"][3] = "short"
231 self
.assertRaises(AssertionError, portfolio
.Portfolio
.parse_cryptoportfolio
)
233 @mock.patch.object(portfolio
.Portfolio
, "get_cryptoportfolio")
234 def test_repartition_pertenthousand(self
, mock_get
):
235 mock_get
.side_effect
= self
.fill_data
237 expected_medium
= {'USDT': 1000, 'ETC': 1000, 'FCT': 1000, 'OMG': 1000, 'STEEM': 1000, 'STRAT': 1000, 'XEM': 1000, 'XMR': 1000, 'XVC': 1000, 'ZRX': 1000}
238 expected_high
= {'USDT': 1226, 'BTC': 1429, 'ETC': 1127, 'ETH': 1569, 'FCT': 3341, 'GAS': 1308}
240 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition_pertenthousand())
241 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition_pertenthousand(liquidity
="medium"))
242 self
.assertEqual(expected_high
, portfolio
.Portfolio
.repartition_pertenthousand(liquidity
="high"))
247 class BalanceTest(unittest
.TestCase
):
249 super(BalanceTest
, self
).setUp()
251 self
.fetch_balance
= {
272 self
.patcher
= mock
.patch
.multiple(portfolio
.Balance
, known_balances
={})
275 def test_values(self
):
276 balance
= portfolio
.Balance("BTC", 0.65, 0.35, 0.30)
277 self
.assertEqual(0.65, balance
.total
.value
)
278 self
.assertEqual(0.35, balance
.free
.value
)
279 self
.assertEqual(0.30, balance
.used
.value
)
280 self
.assertEqual("BTC", balance
.currency
)
282 balance
= portfolio
.Balance
.from_hash("BTC", { "total": 0.65, "free": 0.35, "used": 0.30}
)
283 self
.assertEqual(0.65, balance
.total
.value
)
284 self
.assertEqual(0.35, balance
.free
.value
)
285 self
.assertEqual(0.30, balance
.used
.value
)
286 self
.assertEqual("BTC", balance
.currency
)
288 @mock.patch.object(portfolio
.Trade
, "get_ticker")
289 def test_in_currency(self
, get_ticker
):
290 portfolio
.Balance
.known_balances
= {
291 "BTC": portfolio
.Balance("BTC", "0.65", "0.35", "0.30"),
292 "ETH": portfolio
.Balance("ETH", 3, 3, 0),
295 get_ticker
.return_value
= {
301 amounts
= portfolio
.Balance
.in_currency("BTC", market
)
302 self
.assertEqual("BTC", amounts
["ETH"].currency
)
303 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
304 self
.assertEqual(D("0.30"), amounts
["ETH"].value
)
306 amounts
= portfolio
.Balance
.in_currency("BTC", market
, compute_value
="bid")
307 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
308 self
.assertEqual(D("0.27"), amounts
["ETH"].value
)
310 amounts
= portfolio
.Balance
.in_currency("BTC", market
, compute_value
="bid", type="used")
311 self
.assertEqual(D("0.30"), amounts
["BTC"].value
)
312 self
.assertEqual(0, amounts
["ETH"].value
)
314 def test_currencies(self
):
315 portfolio
.Balance
.known_balances
= {
316 "BTC": portfolio
.Balance("BTC", "0.65", "0.35", "0.30"),
317 "ETH": portfolio
.Balance("ETH", 3, 3, 0),
319 self
.assertListEqual(["BTC", "ETH"], list(portfolio
.Balance
.currencies()))
321 @mock.patch.object(portfolio
.market
, "fetch_balance")
322 def test_fetch_balances(self
, fetch_balance
):
323 fetch_balance
.return_value
= self
.fetch_balance
325 portfolio
.Balance
.fetch_balances(portfolio
.market
)
326 self
.assertNotIn("XMR", portfolio
.Balance
.currencies())
327 self
.assertEqual(["USDT", "XVG"], list(portfolio
.Balance
.currencies()))
329 @mock.patch.object(portfolio
.Portfolio
, "repartition_pertenthousand")
330 @mock.patch.object(portfolio
.market
, "fetch_balance")
331 def test_dispatch_assets(self
, fetch_balance
, repartition
):
332 fetch_balance
.return_value
= self
.fetch_balance
333 portfolio
.Balance
.fetch_balances(portfolio
.market
)
335 self
.assertNotIn("XEM", portfolio
.Balance
.currencies())
337 repartition
.return_value
= {
342 amounts
= portfolio
.Balance
.dispatch_assets(portfolio
.Amount("BTC", "10.1"))
343 self
.assertIn("XEM", portfolio
.Balance
.currencies())
344 self
.assertEqual(D("2.6"), amounts
["BTC"].value
)
345 self
.assertEqual(D("7.5"), amounts
["XEM"].value
)
347 @mock.patch.object(portfolio
.Portfolio
, "repartition_pertenthousand")
348 @mock.patch.object(portfolio
.Trade
, "get_ticker")
349 @mock.patch.object(portfolio
.Trade
, "compute_trades")
350 def test_prepare_trades(self
, compute_trades
, get_ticker
, repartition
):
351 repartition
.return_value
= {
355 def _get_ticker(c1
, c2
, market
):
356 if c1
== "USDT" and c2
== "BTC":
357 return { "average": D("0.0001") }
358 if c1
== "XVG" and c2
== "BTC":
359 return { "average": D("0.000001") }
360 if c1
== "XEM" and c2
== "BTC":
361 return { "average": D("0.001") }
362 raise Exception("Should be called with {}, {}".format(c1
, c2
))
363 get_ticker
.side_effect
= _get_ticker
366 market
.fetch_balance
.return_value
= {
368 "free": D("10000.0"),
370 "total": D("10000.0")
373 "free": D("10000.0"),
375 "total": D("10000.0")
378 portfolio
.Balance
.prepare_trades(market
)
379 compute_trades
.assert_called()
381 call
= compute_trades
.call_args
382 self
.assertEqual(market
, call
[1]["market"])
383 self
.assertEqual(1, call
[0][0]["USDT"].value
)
384 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
385 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
386 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
388 def test__repr(self
):
389 balance
= portfolio
.Balance("BTX", 3, 1, 2)
390 self
.assertEqual("Balance(BTX [1.00000000 BTX/2.00000000 BTX/3.00000000 BTX])", repr(balance
))
395 class TradeTest(unittest
.TestCase
):
399 super(TradeTest
, self
).setUp()
401 self
.patcher
= mock
.patch
.multiple(portfolio
.Trade
,
403 ticker_cache_timestamp
=self
.time
.time(),
408 def test_get_ticker(self
):
410 market
.fetch_ticker
.side_effect
= [
411 { "bid": 1, "ask": 3 }
,
412 portfolio
.ccxt
.ExchangeError("foo"),
413 { "bid": 10, "ask": 40 }
,
414 portfolio
.ccxt
.ExchangeError("foo"),
415 portfolio
.ccxt
.ExchangeError("foo"),
418 ticker
= portfolio
.Trade
.get_ticker("ETH", "ETC", market
)
419 market
.fetch_ticker
.assert_called_with("ETH/ETC")
420 self
.assertEqual(1, ticker
["bid"])
421 self
.assertEqual(3, ticker
["ask"])
422 self
.assertEqual(2, ticker
["average"])
423 self
.assertFalse(ticker
["inverted"])
425 ticker
= portfolio
.Trade
.get_ticker("ETH", "XVG", market
)
426 self
.assertEqual(0.0625, ticker
["average"])
427 self
.assertTrue(ticker
["inverted"])
428 self
.assertIn("original", ticker
)
429 self
.assertEqual(10, ticker
["original"]["bid"])
431 ticker
= portfolio
.Trade
.get_ticker("XVG", "XMR", market
)
432 self
.assertIsNone(ticker
)
434 market
.fetch_ticker
.assert_has_calls([
435 mock
.call("ETH/ETC"),
436 mock
.call("ETH/XVG"),
437 mock
.call("XVG/ETH"),
438 mock
.call("XVG/XMR"),
439 mock
.call("XMR/XVG"),
442 market2
= mock
.Mock()
443 market2
.fetch_ticker
.side_effect
= [
444 { "bid": 1, "ask": 3 }
,
445 { "bid": 1.2, "ask": 3.5 }
,
447 ticker1
= portfolio
.Trade
.get_ticker("ETH", "ETC", market2
)
448 ticker2
= portfolio
.Trade
.get_ticker("ETH", "ETC", market2
)
449 ticker3
= portfolio
.Trade
.get_ticker("ETC", "ETH", market2
)
450 market2
.fetch_ticker
.assert_called_once_with("ETH/ETC")
451 self
.assertEqual(1, ticker1
["bid"])
452 self
.assertDictEqual(ticker1
, ticker2
)
453 self
.assertDictEqual(ticker1
, ticker3
["original"])
455 ticker4
= portfolio
.Trade
.get_ticker("ETH", "ETC", market2
, refresh
=True)
456 ticker5
= portfolio
.Trade
.get_ticker("ETH", "ETC", market2
)
457 self
.assertEqual(1.2, ticker4
["bid"])
458 self
.assertDictEqual(ticker4
, ticker5
)
460 market3
= mock
.Mock()
461 market3
.fetch_ticker
.side_effect
= [
462 { "bid": 1, "ask": 3 }
,
463 { "bid": 1.2, "ask": 3.5 }
,
465 ticker6
= portfolio
.Trade
.get_ticker("ETH", "ETC", market3
)
466 portfolio
.Trade
.ticker_cache_timestamp
-= 4
467 ticker7
= portfolio
.Trade
.get_ticker("ETH", "ETC", market3
)
468 portfolio
.Trade
.ticker_cache_timestamp
-= 2
469 ticker8
= portfolio
.Trade
.get_ticker("ETH", "ETC", market3
)
470 self
.assertDictEqual(ticker6
, ticker7
)
471 self
.assertEqual(1.2, ticker8
["bid"])
473 @unittest.skip("TODO")
474 def test_values_assertion(self
):
477 @unittest.skip("TODO")
478 def test_fetch_fees(self
):
481 @unittest.skip("TODO")
482 def test_compute_trades(self
):
485 @unittest.skip("TODO")
486 def test_action(self
):
489 @unittest.skip("TODO")
490 def test_action(self
):
493 @unittest.skip("TODO")
494 def test_order_action(self
):
497 @unittest.skip("TODO")
498 def test_prepare_order(self
):
501 @unittest.skip("TODO")
502 def test_all_orders(self
):
505 @unittest.skip("TODO")
506 def test_follow_orders(self
):
509 @unittest.skip("TODO")
510 def test_compute_value(self
):
513 @unittest.skip("TODO")
514 def test__repr(self
):
520 if __name__
== '__main__':