]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - test.py
Add compute value lambdas for currency conversion
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
1 import portfolio
2 import unittest
3 from decimal import Decimal as D
4 from unittest import mock
5
6 class AmountTest(unittest.TestCase):
7 def test_values(self):
8 amount = portfolio.Amount("BTC", "0.65")
9 self.assertEqual(D("0.65"), amount.value)
10 self.assertEqual("BTC", amount.currency)
11
12 def test_in_currency(self):
13 amount = portfolio.Amount("ETC", 10)
14
15 self.assertEqual(amount, amount.in_currency("ETC", None))
16
17 ticker_mock = unittest.mock.Mock()
18 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
19 ticker_mock.return_value = None
20
21 self.assertRaises(Exception, amount.in_currency, "ETH", None)
22
23 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
24 ticker_mock.return_value = {
25 "bid": D("0.2"),
26 "ask": D("0.4"),
27 "average": D("0.3"),
28 "foo": "bar",
29 }
30 converted_amount = amount.in_currency("ETH", None)
31
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"])
36
37 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
38 self.assertEqual(D("2"), converted_amount.value)
39
40 converted_amount = amount.in_currency("ETH", None, compute_value="ask")
41 self.assertEqual(D("4"), converted_amount.value)
42
43 def test__abs(self):
44 amount = portfolio.Amount("SC", -120)
45 self.assertEqual(120, abs(amount).value)
46 self.assertEqual("SC", abs(amount).currency)
47
48 amount = portfolio.Amount("SC", 10)
49 self.assertEqual(10, abs(amount).value)
50 self.assertEqual("SC", abs(amount).currency)
51
52 def test__add(self):
53 amount1 = portfolio.Amount("XVG", "12.9")
54 amount2 = portfolio.Amount("XVG", "13.1")
55
56 self.assertEqual(26, (amount1 + amount2).value)
57 self.assertEqual("XVG", (amount1 + amount2).currency)
58
59 amount3 = portfolio.Amount("ETH", "1.6")
60 with self.assertRaises(Exception):
61 amount1 + amount3
62
63 amount4 = portfolio.Amount("ETH", 0.0)
64 self.assertEqual(amount1, amount1 + amount4)
65
66 def test__radd(self):
67 amount = portfolio.Amount("XVG", "12.9")
68
69 self.assertEqual(amount, 0 + amount)
70 with self.assertRaises(Exception):
71 4 + amount
72
73 def test__sub(self):
74 amount1 = portfolio.Amount("XVG", "13.3")
75 amount2 = portfolio.Amount("XVG", "13.1")
76
77 self.assertEqual(D("0.2"), (amount1 - amount2).value)
78 self.assertEqual("XVG", (amount1 - amount2).currency)
79
80 amount3 = portfolio.Amount("ETH", "1.6")
81 with self.assertRaises(Exception):
82 amount1 - amount3
83
84 amount4 = portfolio.Amount("ETH", 0.0)
85 self.assertEqual(amount1, amount1 - amount4)
86
87 def test__mul(self):
88 amount = portfolio.Amount("XEM", 11)
89
90 self.assertEqual(D("38.5"), (amount * D("3.5")).value)
91 self.assertEqual(D("33"), (amount * 3).value)
92
93 with self.assertRaises(Exception):
94 amount * amount
95
96 def test__rmul(self):
97 amount = portfolio.Amount("XEM", 11)
98
99 self.assertEqual(D("38.5"), (D("3.5") * amount).value)
100 self.assertEqual(D("33"), (3 * amount).value)
101
102 def test__floordiv(self):
103 amount = portfolio.Amount("XEM", 11)
104
105 self.assertEqual(D("5.5"), (amount / 2).value)
106 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
107
108 def test__div(self):
109 amount = portfolio.Amount("XEM", 11)
110
111 self.assertEqual(D("5.5"), (amount / 2).value)
112 self.assertEqual(D("4.4"), (amount / D("2.5")).value)
113
114 def test__lt(self):
115 amount1 = portfolio.Amount("BTD", 11.3)
116 amount2 = portfolio.Amount("BTD", 13.1)
117
118 self.assertTrue(amount1 < amount2)
119 self.assertFalse(amount2 < amount1)
120 self.assertFalse(amount1 < amount1)
121
122 amount3 = portfolio.Amount("BTC", 1.6)
123 with self.assertRaises(Exception):
124 amount1 < amount3
125
126 def test__eq(self):
127 amount1 = portfolio.Amount("BTD", 11.3)
128 amount2 = portfolio.Amount("BTD", 13.1)
129 amount3 = portfolio.Amount("BTD", 11.3)
130
131 self.assertFalse(amount1 == amount2)
132 self.assertFalse(amount2 == amount1)
133 self.assertTrue(amount1 == amount3)
134 self.assertFalse(amount2 == 0)
135
136 amount4 = portfolio.Amount("BTC", 1.6)
137 with self.assertRaises(Exception):
138 amount1 == amount4
139
140 amount5 = portfolio.Amount("BTD", 0)
141 self.assertTrue(amount5 == 0)
142
143 def test__str(self):
144 amount1 = portfolio.Amount("BTX", 32)
145 self.assertEqual("32.00000000 BTX", str(amount1))
146
147 amount2 = portfolio.Amount("USDT", 12000)
148 amount1.linked_to = amount2
149 self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))
150
151 def test__repr(self):
152 amount1 = portfolio.Amount("BTX", 32)
153 self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))
154
155 amount2 = portfolio.Amount("USDT", 12000)
156 amount1.linked_to = amount2
157 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))
158
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))
162
163 class PortfolioTest(unittest.TestCase):
164 import urllib3
165 def fill_data(self):
166 if self.json_response is not None:
167 portfolio.Portfolio.data = self.json_response
168
169 def setUp(self):
170 super(PortfolioTest, self).setUp()
171
172 with open("test_portfolio.json") as example:
173 import json
174 self.json_response = json.load(example)
175
176 self.patcher = mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={})
177 self.patcher.start()
178
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"),
187 ]
188
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")
193
194 request.reset_mock()
195 portfolio.Portfolio.get_cryptoportfolio()
196 self.assertIsNone(portfolio.Portfolio.data)
197 request.assert_called_with("GET", "foo://bar")
198
199 request.reset_mock()
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()
205
206 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
207 def test_parse_cryptoportfolio(self, mock_get):
208 mock_get.side_effect = self.fill_data
209
210 portfolio.Portfolio.parse_cryptoportfolio()
211
212 self.assertListEqual(
213 ["medium", "high"],
214 list(portfolio.Portfolio.liquidities.keys()))
215
216 liquidities = portfolio.Portfolio.liquidities
217 self.assertEqual(10, len(liquidities["medium"].keys()))
218 self.assertEqual(10, len(liquidities["high"].keys()))
219
220 expected = {'BTC': 2857, 'DGB': 1015, 'DOGE': 1805, 'SC': 623, 'ZEC': 3701}
221 self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])
222
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'])
225
226 # It doesn't refetch the data when available
227 portfolio.Portfolio.parse_cryptoportfolio()
228 mock_get.assert_called_once_with()
229
230 portfolio.Portfolio.data["portfolio_1"]["holding"]["direction"][3] = "short"
231 self.assertRaises(AssertionError, portfolio.Portfolio.parse_cryptoportfolio)
232
233 @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio")
234 def test_repartition_pertenthousand(self, mock_get):
235 mock_get.side_effect = self.fill_data
236
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}
239
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"))
243
244 def tearDown(self):
245 self.patcher.stop()
246
247 class BalanceTest(unittest.TestCase):
248 def setUp(self):
249 super(BalanceTest, self).setUp()
250
251 self.fetch_balance = {
252 "free": "foo",
253 "info": "bar",
254 "used": "baz",
255 "total": "bazz",
256 "USDT": {
257 "free": 6.0,
258 "used": 1.2,
259 "total": 7.2
260 },
261 "XVG": {
262 "free": 16,
263 "used": 0.0,
264 "total": 16
265 },
266 "XMR": {
267 "free": 0.0,
268 "used": 0.0,
269 "total": 0.0
270 },
271 }
272 self.patcher = mock.patch.multiple(portfolio.Balance, known_balances={})
273 self.patcher.start()
274
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)
281
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)
287
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),
293 }
294 market = mock.Mock()
295 get_ticker.return_value = {
296 "bid": D("0.09"),
297 "ask": D("0.11"),
298 "average": D("0.1"),
299 }
300
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)
305
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)
309
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)
313
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),
318 }
319 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
320
321 @mock.patch.object(portfolio.market, "fetch_balance")
322 def test_fetch_balances(self, fetch_balance):
323 fetch_balance.return_value = self.fetch_balance
324
325 portfolio.Balance.fetch_balances(portfolio.market)
326 self.assertNotIn("XMR", portfolio.Balance.currencies())
327 self.assertEqual(["USDT", "XVG"], list(portfolio.Balance.currencies()))
328
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)
334
335 self.assertNotIn("XEM", portfolio.Balance.currencies())
336
337 repartition.return_value = {
338 "XEM": 7500,
339 "BTC": 2600,
340 }
341
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)
346
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 = {
352 "XEM": 7500,
353 "BTC": 2500,
354 }
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
364
365 market = mock.Mock()
366 market.fetch_balance.return_value = {
367 "USDT": {
368 "free": D("10000.0"),
369 "used": D("0.0"),
370 "total": D("10000.0")
371 },
372 "XVG": {
373 "free": D("10000.0"),
374 "used": D("0.0"),
375 "total": D("10000.0")
376 },
377 }
378 portfolio.Balance.prepare_trades(market)
379 compute_trades.assert_called()
380
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)
387
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))
391
392 def tearDown(self):
393 self.patcher.stop()
394
395 class TradeTest(unittest.TestCase):
396 import time
397
398 def setUp(self):
399 super(TradeTest, self).setUp()
400
401 self.patcher = mock.patch.multiple(portfolio.Trade,
402 ticker_cache={},
403 ticker_cache_timestamp=self.time.time(),
404 fees_cache={},
405 trades={})
406 self.patcher.start()
407
408 def test_get_ticker(self):
409 market = mock.Mock()
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"),
416 ]
417
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"])
424
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"])
430
431 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market)
432 self.assertIsNone(ticker)
433
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"),
440 ])
441
442 market2 = mock.Mock()
443 market2.fetch_ticker.side_effect = [
444 { "bid": 1, "ask": 3 },
445 { "bid": 1.2, "ask": 3.5 },
446 ]
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"])
454
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)
459
460 market3 = mock.Mock()
461 market3.fetch_ticker.side_effect = [
462 { "bid": 1, "ask": 3 },
463 { "bid": 1.2, "ask": 3.5 },
464 ]
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"])
472
473 @unittest.skip("TODO")
474 def test_values_assertion(self):
475 pass
476
477 @unittest.skip("TODO")
478 def test_fetch_fees(self):
479 pass
480
481 @unittest.skip("TODO")
482 def test_compute_trades(self):
483 pass
484
485 @unittest.skip("TODO")
486 def test_action(self):
487 pass
488
489 @unittest.skip("TODO")
490 def test_action(self):
491 pass
492
493 @unittest.skip("TODO")
494 def test_order_action(self):
495 pass
496
497 @unittest.skip("TODO")
498 def test_prepare_order(self):
499 pass
500
501 @unittest.skip("TODO")
502 def test_all_orders(self):
503 pass
504
505 @unittest.skip("TODO")
506 def test_follow_orders(self):
507 pass
508
509 @unittest.skip("TODO")
510 def test_compute_value(self):
511 pass
512
513 @unittest.skip("TODO")
514 def test__repr(self):
515 pass
516
517 def tearDown(self):
518 self.patcher.stop()
519
520 if __name__ == '__main__':
521 unittest.main()