4 from decimal
import Decimal
as D
5 from unittest
import mock
8 from io
import StringIO
11 limits
= ["acceptance", "unit"]
12 for test_type
in limits
:
13 if "--no{}".format(test_type
) in sys
.argv
:
14 sys
.argv
.remove("--no{}".format(test_type
))
15 limits
.remove(test_type
)
16 if "--only{}".format(test_type
) in sys
.argv
:
17 sys
.argv
.remove("--only{}".format(test_type
))
21 class WebMockTestCase(unittest
.TestCase
):
25 super(WebMockTestCase
, self
).setUp()
26 self
.wm
= requests_mock
.Mocker()
30 mock
.patch
.multiple(portfolio
.ReportStore
,
31 logs
=[], verbose_print
=True),
32 mock
.patch
.multiple(portfolio
.BalanceStore
,
34 mock
.patch
.multiple(portfolio
.TradeStore
,
37 mock
.patch
.multiple(portfolio
.Portfolio
, last_date
=None, data
=None, liquidities
={}),
38 mock
.patch
.multiple(portfolio
.Computation
,
39 computations
=portfolio
.Computation
.computations
),
40 mock
.patch
.multiple(helper
,
43 ticker_cache_timestamp
=self
.time
.time()),
45 for patcher
in self
.patchers
:
49 for patcher
in self
.patchers
:
52 super(WebMockTestCase
, self
).tearDown()
54 @unittest.skipUnless("unit" in limits
, "Unit skipped")
55 class PortfolioTest(WebMockTestCase
):
57 if self
.json_response
is not None:
58 portfolio
.Portfolio
.data
= self
.json_response
61 super(PortfolioTest
, self
).setUp()
63 with open("test_portfolio.json") as example
:
64 self
.json_response
= example
.read()
66 self
.wm
.get(portfolio
.Portfolio
.URL
, text
=self
.json_response
)
68 @mock.patch("portfolio.ReportStore")
69 def test_get_cryptoportfolio(self
, report_store
):
70 self
.wm
.get(portfolio
.Portfolio
.URL
, [
71 {"text":'{ "foo": "bar" }
', "status_code": 200},
72 {"text": "System Error", "status_code": 500},
73 {"exc": requests.exceptions.ConnectTimeout},
75 portfolio.Portfolio.get_cryptoportfolio()
76 self.assertIn("foo", portfolio.Portfolio.data)
77 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
78 self.assertTrue(self.wm.called)
79 self.assertEqual(1, self.wm.call_count)
80 report_store.log_error.assert_not_called()
81 report_store.log_http_request.assert_called_once()
82 report_store.log_http_request.reset_mock()
84 portfolio.Portfolio.get_cryptoportfolio()
85 self.assertIsNone(portfolio.Portfolio.data)
86 self.assertEqual(2, self.wm.call_count)
87 report_store.log_error.assert_not_called()
88 report_store.log_http_request.assert_called_once()
89 report_store.log_http_request.reset_mock()
92 portfolio.Portfolio.data = "Foo"
93 portfolio.Portfolio.get_cryptoportfolio()
94 self.assertEqual("Foo", portfolio.Portfolio.data)
95 self.assertEqual(3, self.wm.call_count)
96 report_store.log_error.assert_called_once_with("get_cryptoportfolio",
98 report_store.log_http_request.assert_not_called()
100 @mock.patch("portfolio.ReportStore")
101 def test_parse_cryptoportfolio(self, report_store):
102 portfolio.Portfolio.parse_cryptoportfolio()
104 self.assertListEqual(
106 list(portfolio.Portfolio.liquidities.keys()))
108 liquidities = portfolio.Portfolio.liquidities
109 self.assertEqual(10, len(liquidities["medium"].keys()))
110 self.assertEqual(10, len(liquidities["high"].keys()))
113 'BTC
': (D("0.2857"), "long"),
114 'DGB
': (D("0.1015"), "long"),
115 'DOGE
': (D("0.1805"), "long"),
116 'SC
': (D("0.0623"), "long"),
117 'ZEC
': (D("0.3701"), "long"),
119 date = portfolio.datetime(2018, 1, 8)
120 self.assertDictEqual(expected, liquidities["high"][date])
123 'BTC
': (D("1.1102e-16"), "long"),
124 'ETC
': (D("0.1"), "long"),
125 'FCT
': (D("0.1"), "long"),
126 'GAS
': (D("0.1"), "long"),
127 'NAV
': (D("0.1"), "long"),
128 'OMG
': (D("0.1"), "long"),
129 'OMNI
': (D("0.1"), "long"),
130 'PPC
': (D("0.1"), "long"),
131 'RIC
': (D("0.1"), "long"),
132 'VIA
': (D("0.1"), "long"),
133 'XCP
': (D("0.1"), "long"),
135 self.assertDictEqual(expected, liquidities["medium"][date])
136 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)
138 report_store.log_http_request.assert_called_once_with("GET",
139 portfolio.Portfolio.URL, None, mock.ANY, mock.ANY)
140 report_store.log_http_request.reset_mock()
142 # It doesn't refetch the data when available
143 portfolio
.Portfolio
.parse_cryptoportfolio()
144 report_store
.log_http_request
.assert_not_called()
146 self
.assertEqual(1, self
.wm
.call_count
)
148 portfolio
.Portfolio
.parse_cryptoportfolio(refetch
=True)
149 self
.assertEqual(2, self
.wm
.call_count
)
150 report_store
.log_http_request
.assert_called_once()
152 @mock.patch("portfolio.ReportStore")
153 def test_repartition(self
, report_store
):
155 'BTC': (D("1.1102e-16"), "long"),
156 'USDT': (D("0.1"), "long"),
157 'ETC': (D("0.1"), "long"),
158 'FCT': (D("0.1"), "long"),
159 'OMG': (D("0.1"), "long"),
160 'STEEM': (D("0.1"), "long"),
161 'STRAT': (D("0.1"), "long"),
162 'XEM': (D("0.1"), "long"),
163 'XMR': (D("0.1"), "long"),
164 'XVC': (D("0.1"), "long"),
165 'ZRX': (D("0.1"), "long"),
168 'USDT': (D("0.1226"), "long"),
169 'BTC': (D("0.1429"), "long"),
170 'ETC': (D("0.1127"), "long"),
171 'ETH': (D("0.1569"), "long"),
172 'FCT': (D("0.3341"), "long"),
173 'GAS': (D("0.1308"), "long"),
176 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition())
177 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition(liquidity
="medium"))
178 self
.assertEqual(expected_high
, portfolio
.Portfolio
.repartition(liquidity
="high"))
180 self
.assertEqual(1, self
.wm
.call_count
)
182 portfolio
.Portfolio
.repartition()
183 self
.assertEqual(1, self
.wm
.call_count
)
185 portfolio
.Portfolio
.repartition(refetch
=True)
186 self
.assertEqual(2, self
.wm
.call_count
)
187 report_store
.log_http_request
.assert_called()
188 self
.assertEqual(2, report_store
.log_http_request
.call_count
)
190 @mock.patch.object(portfolio
.time
, "sleep")
191 @mock.patch.object(portfolio
.Portfolio
, "repartition")
192 def test_wait_for_recent(self
, repartition
, sleep
):
194 def _repartition(refetch
):
195 self
.assertTrue(refetch
)
197 portfolio
.Portfolio
.last_date
= portfolio
.datetime
.now()\
198 - portfolio
.timedelta(10)\
199 + portfolio
.timedelta(self
.call_count
)
200 repartition
.side_effect
= _repartition
202 portfolio
.Portfolio
.wait_for_recent()
203 sleep
.assert_called_with(30)
204 self
.assertEqual(6, sleep
.call_count
)
205 self
.assertEqual(7, repartition
.call_count
)
208 repartition
.reset_mock()
209 portfolio
.Portfolio
.last_date
= None
211 portfolio
.Portfolio
.wait_for_recent(delta
=15)
212 sleep
.assert_not_called()
213 self
.assertEqual(1, repartition
.call_count
)
216 repartition
.reset_mock()
217 portfolio
.Portfolio
.last_date
= None
219 portfolio
.Portfolio
.wait_for_recent(delta
=1)
220 sleep
.assert_called_with(30)
221 self
.assertEqual(9, sleep
.call_count
)
222 self
.assertEqual(10, repartition
.call_count
)
224 @unittest.skipUnless("unit" in limits
, "Unit skipped")
225 class AmountTest(WebMockTestCase
):
226 def test_values(self
):
227 amount
= portfolio
.Amount("BTC", "0.65")
228 self
.assertEqual(D("0.65"), amount
.value
)
229 self
.assertEqual("BTC", amount
.currency
)
231 def test_in_currency(self
):
232 amount
= portfolio
.Amount("ETC", 10)
234 self
.assertEqual(amount
, amount
.in_currency("ETC", None))
236 ticker_mock
= unittest
.mock
.Mock()
237 with mock
.patch
.object(helper
, 'get_ticker', new
=ticker_mock
):
238 ticker_mock
.return_value
= None
240 self
.assertRaises(Exception, amount
.in_currency
, "ETH", None)
242 with mock
.patch
.object(helper
, 'get_ticker', new
=ticker_mock
):
243 ticker_mock
.return_value
= {
249 converted_amount
= amount
.in_currency("ETH", None)
251 self
.assertEqual(D("3.0"), converted_amount
.value
)
252 self
.assertEqual("ETH", converted_amount
.currency
)
253 self
.assertEqual(amount
, converted_amount
.linked_to
)
254 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
256 converted_amount
= amount
.in_currency("ETH", None, action
="bid", compute_value
="default")
257 self
.assertEqual(D("2"), converted_amount
.value
)
259 converted_amount
= amount
.in_currency("ETH", None, compute_value
="ask")
260 self
.assertEqual(D("4"), converted_amount
.value
)
262 converted_amount
= amount
.in_currency("ETH", None, rate
=D("0.02"))
263 self
.assertEqual(D("0.2"), converted_amount
.value
)
265 def test__round(self
):
266 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
267 self
.assertEqual(D("1.23456789"), round(amount
).value
)
268 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
271 amount
= portfolio
.Amount("SC", -120)
272 self
.assertEqual(120, abs(amount
).value
)
273 self
.assertEqual("SC", abs(amount
).currency
)
275 amount
= portfolio
.Amount("SC", 10)
276 self
.assertEqual(10, abs(amount
).value
)
277 self
.assertEqual("SC", abs(amount
).currency
)
280 amount1
= portfolio
.Amount("XVG", "12.9")
281 amount2
= portfolio
.Amount("XVG", "13.1")
283 self
.assertEqual(26, (amount1
+ amount2
).value
)
284 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
286 amount3
= portfolio
.Amount("ETH", "1.6")
287 with self
.assertRaises(Exception):
290 amount4
= portfolio
.Amount("ETH", 0.0)
291 self
.assertEqual(amount1
, amount1
+ amount4
)
293 self
.assertEqual(amount1
, amount1
+ 0)
295 def test__radd(self
):
296 amount
= portfolio
.Amount("XVG", "12.9")
298 self
.assertEqual(amount
, 0 + amount
)
299 with self
.assertRaises(Exception):
303 amount1
= portfolio
.Amount("XVG", "13.3")
304 amount2
= portfolio
.Amount("XVG", "13.1")
306 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
307 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
309 amount3
= portfolio
.Amount("ETH", "1.6")
310 with self
.assertRaises(Exception):
313 amount4
= portfolio
.Amount("ETH", 0.0)
314 self
.assertEqual(amount1
, amount1
- amount4
)
316 def test__rsub(self
):
317 amount
= portfolio
.Amount("ETH", "1.6")
318 with self
.assertRaises(Exception):
321 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), -amount
)
324 amount
= portfolio
.Amount("XEM", 11)
326 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
327 self
.assertEqual(D("33"), (amount
* 3).value
)
329 with self
.assertRaises(Exception):
332 def test__rmul(self
):
333 amount
= portfolio
.Amount("XEM", 11)
335 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
336 self
.assertEqual(D("33"), (3 * amount
).value
)
338 def test__floordiv(self
):
339 amount
= portfolio
.Amount("XEM", 11)
341 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
342 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
344 with self
.assertRaises(Exception):
347 def test__truediv(self
):
348 amount
= portfolio
.Amount("XEM", 11)
350 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
351 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
354 amount1
= portfolio
.Amount("BTD", 11.3)
355 amount2
= portfolio
.Amount("BTD", 13.1)
357 self
.assertTrue(amount1
< amount2
)
358 self
.assertFalse(amount2
< amount1
)
359 self
.assertFalse(amount1
< amount1
)
361 amount3
= portfolio
.Amount("BTC", 1.6)
362 with self
.assertRaises(Exception):
366 amount1
= portfolio
.Amount("BTD", 11.3)
367 amount2
= portfolio
.Amount("BTD", 13.1)
369 self
.assertTrue(amount1
<= amount2
)
370 self
.assertFalse(amount2
<= amount1
)
371 self
.assertTrue(amount1
<= amount1
)
373 amount3
= portfolio
.Amount("BTC", 1.6)
374 with self
.assertRaises(Exception):
378 amount1
= portfolio
.Amount("BTD", 11.3)
379 amount2
= portfolio
.Amount("BTD", 13.1)
381 self
.assertTrue(amount2
> amount1
)
382 self
.assertFalse(amount1
> amount2
)
383 self
.assertFalse(amount1
> amount1
)
385 amount3
= portfolio
.Amount("BTC", 1.6)
386 with self
.assertRaises(Exception):
390 amount1
= portfolio
.Amount("BTD", 11.3)
391 amount2
= portfolio
.Amount("BTD", 13.1)
393 self
.assertTrue(amount2
>= amount1
)
394 self
.assertFalse(amount1
>= amount2
)
395 self
.assertTrue(amount1
>= amount1
)
397 amount3
= portfolio
.Amount("BTC", 1.6)
398 with self
.assertRaises(Exception):
402 amount1
= portfolio
.Amount("BTD", 11.3)
403 amount2
= portfolio
.Amount("BTD", 13.1)
404 amount3
= portfolio
.Amount("BTD", 11.3)
406 self
.assertFalse(amount1
== amount2
)
407 self
.assertFalse(amount2
== amount1
)
408 self
.assertTrue(amount1
== amount3
)
409 self
.assertFalse(amount2
== 0)
411 amount4
= portfolio
.Amount("BTC", 1.6)
412 with self
.assertRaises(Exception):
415 amount5
= portfolio
.Amount("BTD", 0)
416 self
.assertTrue(amount5
== 0)
419 amount1
= portfolio
.Amount("BTD", 11.3)
420 amount2
= portfolio
.Amount("BTD", 13.1)
421 amount3
= portfolio
.Amount("BTD", 11.3)
423 self
.assertTrue(amount1
!= amount2
)
424 self
.assertTrue(amount2
!= amount1
)
425 self
.assertFalse(amount1
!= amount3
)
426 self
.assertTrue(amount2
!= 0)
428 amount4
= portfolio
.Amount("BTC", 1.6)
429 with self
.assertRaises(Exception):
432 amount5
= portfolio
.Amount("BTD", 0)
433 self
.assertFalse(amount5
!= 0)
436 amount1
= portfolio
.Amount("BTD", "11.3")
438 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
441 amount1
= portfolio
.Amount("BTX", 32)
442 self
.assertEqual("32.00000000 BTX", str(amount1
))
444 amount2
= portfolio
.Amount("USDT", 12000)
445 amount1
.linked_to
= amount2
446 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
448 def test__repr(self
):
449 amount1
= portfolio
.Amount("BTX", 32)
450 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
452 amount2
= portfolio
.Amount("USDT", 12000)
453 amount1
.linked_to
= amount2
454 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
456 amount3
= portfolio
.Amount("BTC", 0.1)
457 amount2
.linked_to
= amount3
458 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
460 def test_as_json(self
):
461 amount
= portfolio
.Amount("BTX", 32)
462 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
464 amount
= portfolio
.Amount("BTX", "1E-10")
465 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
467 amount
= portfolio
.Amount("BTX", "1E-5")
468 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
469 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))
471 @unittest.skipUnless("unit" in limits
, "Unit skipped")
472 class BalanceTest(WebMockTestCase
):
473 def test_values(self
):
474 balance
= portfolio
.Balance("BTC", {
475 "exchange_total": "0.65",
476 "exchange_free": "0.35",
477 "exchange_used": "0.30",
478 "margin_total": "-10",
479 "margin_borrowed": "-10",
481 "margin_position_type": "short",
482 "margin_borrowed_base_currency": "USDT",
483 "margin_liquidation_price": "1.20",
484 "margin_pending_gain": "10",
485 "margin_lending_fees": "0.4",
486 "margin_borrowed_base_price": "0.15",
488 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
489 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
490 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
491 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
492 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
493 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
495 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
496 self
.assertEqual(portfolio
.D("-10"), balance
.margin_borrowed
.value
)
497 self
.assertEqual(portfolio
.D("0"), balance
.margin_free
.value
)
498 self
.assertEqual("BTC", balance
.margin_total
.currency
)
499 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
500 self
.assertEqual("BTC", balance
.margin_free
.currency
)
502 self
.assertEqual("BTC", balance
.currency
)
504 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
505 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
507 def test__repr(self
):
508 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
509 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
510 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
511 "exchange_used": 1, "exchange_free": 2 })
512 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
514 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
515 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
517 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
518 "margin_borrowed": 1, "margin_free": 2 })
519 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
521 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_free": 2 }
)
522 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
524 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
525 "margin_borrowed_base_price": D("0.1"),
526 "margin_borrowed_base_currency": "BTC",
527 "margin_lending_fees": D("0.002") })
528 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
530 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
531 "margin_borrowed": 1, "exchange_free": 2, "exchange_total": 2})
532 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
534 def test_as_json(self
):
535 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
536 as_json
= balance
.as_json()
537 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
538 self
.assertEqual(D(0), as_json
["total"])
539 self
.assertEqual(D(2), as_json
["exchange_total"])
540 self
.assertEqual(D(2), as_json
["exchange_free"])
541 self
.assertEqual(D(0), as_json
["exchange_used"])
542 self
.assertEqual(D(0), as_json
["margin_total"])
543 self
.assertEqual(D(0), as_json
["margin_free"])
544 self
.assertEqual(D(0), as_json
["margin_borrowed"])
546 @unittest.skipUnless("unit" in limits
, "Unit skipped")
547 class HelperTest(WebMockTestCase
):
548 def test_get_ticker(self
):
550 market
.fetch_ticker
.side_effect
= [
551 { "bid": 1, "ask": 3 }
,
552 helper
.ExchangeError("foo"),
553 { "bid": 10, "ask": 40 }
,
554 helper
.ExchangeError("foo"),
555 helper
.ExchangeError("foo"),
558 ticker
= helper
.get_ticker("ETH", "ETC", market
)
559 market
.fetch_ticker
.assert_called_with("ETH/ETC")
560 self
.assertEqual(1, ticker
["bid"])
561 self
.assertEqual(3, ticker
["ask"])
562 self
.assertEqual(2, ticker
["average"])
563 self
.assertFalse(ticker
["inverted"])
565 ticker
= helper
.get_ticker("ETH", "XVG", market
)
566 self
.assertEqual(0.0625, ticker
["average"])
567 self
.assertTrue(ticker
["inverted"])
568 self
.assertIn("original", ticker
)
569 self
.assertEqual(10, ticker
["original"]["bid"])
571 ticker
= helper
.get_ticker("XVG", "XMR", market
)
572 self
.assertIsNone(ticker
)
574 market
.fetch_ticker
.assert_has_calls([
575 mock
.call("ETH/ETC"),
576 mock
.call("ETH/XVG"),
577 mock
.call("XVG/ETH"),
578 mock
.call("XVG/XMR"),
579 mock
.call("XMR/XVG"),
582 market2
= mock
.Mock()
583 market2
.fetch_ticker
.side_effect
= [
584 { "bid": 1, "ask": 3 }
,
585 { "bid": 1.2, "ask": 3.5 }
,
587 ticker1
= helper
.get_ticker("ETH", "ETC", market2
)
588 ticker2
= helper
.get_ticker("ETH", "ETC", market2
)
589 ticker3
= helper
.get_ticker("ETC", "ETH", market2
)
590 market2
.fetch_ticker
.assert_called_once_with("ETH/ETC")
591 self
.assertEqual(1, ticker1
["bid"])
592 self
.assertDictEqual(ticker1
, ticker2
)
593 self
.assertDictEqual(ticker1
, ticker3
["original"])
595 ticker4
= helper
.get_ticker("ETH", "ETC", market2
, refresh
=True)
596 ticker5
= helper
.get_ticker("ETH", "ETC", market2
)
597 self
.assertEqual(1.2, ticker4
["bid"])
598 self
.assertDictEqual(ticker4
, ticker5
)
600 market3
= mock
.Mock()
601 market3
.fetch_ticker
.side_effect
= [
602 { "bid": 1, "ask": 3 }
,
603 { "bid": 1.2, "ask": 3.5 }
,
605 ticker6
= helper
.get_ticker("ETH", "ETC", market3
)
606 helper
.ticker_cache_timestamp
-= 4
607 ticker7
= helper
.get_ticker("ETH", "ETC", market3
)
608 helper
.ticker_cache_timestamp
-= 2
609 ticker8
= helper
.get_ticker("ETH", "ETC", market3
)
610 self
.assertDictEqual(ticker6
, ticker7
)
611 self
.assertEqual(1.2, ticker8
["bid"])
613 def test_fetch_fees(self
):
615 market
.fetch_fees
.return_value
= "Foo"
616 self
.assertEqual("Foo", helper
.fetch_fees(market
))
617 market
.fetch_fees
.assert_called_once()
618 self
.assertEqual("Foo", helper
.fetch_fees(market
))
619 market
.fetch_fees
.assert_called_once()
621 @mock.patch.object(portfolio
.Portfolio
, "repartition")
622 @mock.patch.object(helper
, "get_ticker")
623 @mock.patch.object(portfolio
.TradeStore
, "compute_trades")
624 @mock.patch("store.ReportStore")
625 @mock.patch("helper.ReportStore")
626 def test_prepare_trades(self
, report_store_h
, report_store
, compute_trades
, get_ticker
, repartition
):
627 repartition
.return_value
= {
628 "XEM": (D("0.75"), "long"),
629 "BTC": (D("0.25"), "long"),
631 def _get_ticker(c1
, c2
, market
):
632 if c1
== "USDT" and c2
== "BTC":
633 return { "average": D("0.0001") }
634 if c1
== "XVG" and c2
== "BTC":
635 return { "average": D("0.000001") }
636 if c1
== "XEM" and c2
== "BTC":
637 return { "average": D("0.001") }
638 self
.fail("Should be called with {}, {}".format(c1
, c2
))
639 get_ticker
.side_effect
= _get_ticker
642 market
.fetch_all_balances
.return_value
= {
644 "exchange_free": D("10000.0"),
645 "exchange_used": D("0.0"),
646 "exchange_total": D("10000.0"),
647 "total": D("10000.0")
650 "exchange_free": D("10000.0"),
651 "exchange_used": D("0.0"),
652 "exchange_total": D("10000.0"),
653 "total": D("10000.0")
656 portfolio
.BalanceStore
.fetch_balances(market
, tag
="tag")
658 helper
.prepare_trades(market
)
659 compute_trades
.assert_called()
661 call
= compute_trades
.call_args
662 self
.assertEqual(market
, call
[1]["market"])
663 self
.assertEqual(1, call
[0][0]["USDT"].value
)
664 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
665 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
666 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
667 report_store_h
.log_stage
.assert_called_once_with("prepare_trades")
668 report_store
.log_balances
.assert_called_once_with(market
, tag
="tag")
670 @mock.patch.object(portfolio
.Portfolio
, "repartition")
671 @mock.patch.object(helper
, "get_ticker")
672 @mock.patch.object(portfolio
.TradeStore
, "compute_trades")
673 @mock.patch("store.ReportStore")
674 @mock.patch("helper.ReportStore")
675 def test_update_trades(self
, report_store_h
, report_store
, compute_trades
, get_ticker
, repartition
):
676 repartition
.return_value
= {
677 "XEM": (D("0.75"), "long"),
678 "BTC": (D("0.25"), "long"),
680 def _get_ticker(c1
, c2
, market
):
681 if c1
== "USDT" and c2
== "BTC":
682 return { "average": D("0.0001") }
683 if c1
== "XVG" and c2
== "BTC":
684 return { "average": D("0.000001") }
685 if c1
== "XEM" and c2
== "BTC":
686 return { "average": D("0.001") }
687 self
.fail("Should be called with {}, {}".format(c1
, c2
))
688 get_ticker
.side_effect
= _get_ticker
691 market
.fetch_all_balances
.return_value
= {
693 "exchange_free": D("10000.0"),
694 "exchange_used": D("0.0"),
695 "exchange_total": D("10000.0"),
696 "total": D("10000.0")
699 "exchange_free": D("10000.0"),
700 "exchange_used": D("0.0"),
701 "exchange_total": D("10000.0"),
702 "total": D("10000.0")
705 portfolio
.BalanceStore
.fetch_balances(market
, tag
="tag")
707 helper
.update_trades(market
)
708 compute_trades
.assert_called()
710 call
= compute_trades
.call_args
711 self
.assertEqual(market
, call
[1]["market"])
712 self
.assertEqual(1, call
[0][0]["USDT"].value
)
713 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
714 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
715 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
716 report_store_h
.log_stage
.assert_called_once_with("update_trades")
717 report_store
.log_balances
.assert_called_once_with(market
, tag
="tag")
719 @mock.patch.object(portfolio
.Portfolio
, "repartition")
720 @mock.patch.object(helper
, "get_ticker")
721 @mock.patch.object(portfolio
.TradeStore
, "compute_trades")
722 @mock.patch("store.ReportStore")
723 @mock.patch("helper.ReportStore")
724 def test_prepare_trades_to_sell_all(self
, report_store_h
, report_store
, compute_trades
, get_ticker
, repartition
):
725 def _get_ticker(c1
, c2
, market
):
726 if c1
== "USDT" and c2
== "BTC":
727 return { "average": D("0.0001") }
728 if c1
== "XVG" and c2
== "BTC":
729 return { "average": D("0.000001") }
730 self
.fail("Should be called with {}, {}".format(c1
, c2
))
731 get_ticker
.side_effect
= _get_ticker
734 market
.fetch_all_balances
.return_value
= {
736 "exchange_free": D("10000.0"),
737 "exchange_used": D("0.0"),
738 "exchange_total": D("10000.0"),
739 "total": D("10000.0")
742 "exchange_free": D("10000.0"),
743 "exchange_used": D("0.0"),
744 "exchange_total": D("10000.0"),
745 "total": D("10000.0")
748 portfolio
.BalanceStore
.fetch_balances(market
, tag
="tag")
750 helper
.prepare_trades_to_sell_all(market
)
751 repartition
.assert_not_called()
752 compute_trades
.assert_called()
754 call
= compute_trades
.call_args
755 self
.assertEqual(market
, call
[1]["market"])
756 self
.assertEqual(1, call
[0][0]["USDT"].value
)
757 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
758 self
.assertEqual(D("1.01"), call
[0][1]["BTC"].value
)
759 report_store_h
.log_stage
.assert_called_once_with("prepare_trades_to_sell_all")
760 report_store
.log_balances
.assert_called_once_with(market
, tag
="tag")
762 @mock.patch.object(portfolio
.time
, "sleep")
763 @mock.patch.object(portfolio
.TradeStore
, "all_orders")
764 def test_follow_orders(self
, all_orders
, time_mock
):
765 for debug
, sleep
in [
766 (False, None), (True, None),
767 (False, 12), (True, 12)]:
768 with self
.subTest(sleep
=sleep
, debug
=debug
), \
769 mock
.patch("helper.ReportStore") as report_store
:
770 portfolio
.TradeStore
.debug
= debug
771 order_mock1
= mock
.Mock()
772 order_mock2
= mock
.Mock()
773 order_mock3
= mock
.Mock()
774 all_orders
.side_effect
= [
775 [order_mock1
, order_mock2
],
776 [order_mock1
, order_mock2
],
778 [order_mock1
, order_mock3
],
779 [order_mock1
, order_mock3
],
781 [order_mock1
, order_mock3
],
782 [order_mock1
, order_mock3
],
787 order_mock1
.get_status
.side_effect
= ["open", "open", "closed"]
788 order_mock2
.get_status
.side_effect
= ["open"]
789 order_mock3
.get_status
.side_effect
= ["open", "closed"]
791 order_mock1
.trade
= mock
.Mock()
792 order_mock2
.trade
= mock
.Mock()
793 order_mock3
.trade
= mock
.Mock()
795 helper
.follow_orders(sleep
=sleep
)
797 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 1)
798 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 2)
799 self
.assertEqual(2, order_mock1
.trade
.update_order
.call_count
)
800 self
.assertEqual(3, order_mock1
.get_status
.call_count
)
802 order_mock2
.trade
.update_order
.assert_any_call(order_mock2
, 1)
803 self
.assertEqual(1, order_mock2
.trade
.update_order
.call_count
)
804 self
.assertEqual(1, order_mock2
.get_status
.call_count
)
806 order_mock3
.trade
.update_order
.assert_any_call(order_mock3
, 2)
807 self
.assertEqual(1, order_mock3
.trade
.update_order
.call_count
)
808 self
.assertEqual(2, order_mock3
.get_status
.call_count
)
809 report_store
.log_stage
.assert_called()
811 mock
.call("follow_orders_begin"),
812 mock
.call("follow_orders_tick_1"),
813 mock
.call("follow_orders_tick_2"),
814 mock
.call("follow_orders_tick_3"),
815 mock
.call("follow_orders_end"),
817 report_store
.log_stage
.assert_has_calls(calls
)
818 report_store
.log_orders
.assert_called()
819 self
.assertEqual(3, report_store
.log_orders
.call_count
)
821 mock
.call([order_mock1
, order_mock2
], tick
=1),
822 mock
.call([order_mock1
, order_mock3
], tick
=2),
823 mock
.call([order_mock1
, order_mock3
], tick
=3),
825 report_store
.log_orders
.assert_has_calls(calls
)
827 mock
.call(order_mock1
, 3, finished
=True),
828 mock
.call(order_mock3
, 3, finished
=True),
830 report_store
.log_order
.assert_has_calls(calls
)
834 report_store
.log_debug_action
.assert_called_with("Set follow_orders tick to 7s")
835 time_mock
.assert_called_with(7)
837 time_mock
.assert_called_with(30)
839 time_mock
.assert_called_with(sleep
)
841 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
842 def test_move_balance(self
, fetch_balances
):
843 for debug
in [True, False]:
844 with self
.subTest(debug
=debug
),\
845 mock
.patch("helper.ReportStore") as report_store
:
846 value_from
= portfolio
.Amount("BTC", "1.0")
847 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
848 value_to
= portfolio
.Amount("BTC", "10.0")
849 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH")
851 value_from
= portfolio
.Amount("BTC", "0.0")
852 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
853 value_to
= portfolio
.Amount("BTC", "-3.0")
854 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH")
856 value_from
= portfolio
.Amount("USDT", "0.0")
857 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
858 value_to
= portfolio
.Amount("USDT", "-50.0")
859 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG")
861 portfolio
.TradeStore
.all
= [trade1
, trade2
, trade3
]
862 balance1
= portfolio
.Balance("BTC", { "margin_free": "0" }
)
863 balance2
= portfolio
.Balance("USDT", { "margin_free": "100" }
)
864 balance3
= portfolio
.Balance("ETC", { "margin_free": "10" }
)
865 portfolio
.BalanceStore
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
869 helper
.move_balances(market
, debug
=debug
)
871 fetch_balances
.assert_called_with(market
)
872 report_store
.log_move_balances
.assert_called_once()
875 report_store
.log_debug_action
.assert_called()
876 self
.assertEqual(3, report_store
.log_debug_action
.call_count
)
878 market
.transfer_balance
.assert_any_call("BTC", 3, "exchange", "margin")
879 market
.transfer_balance
.assert_any_call("USDT", 50, "margin", "exchange")
880 market
.transfer_balance
.assert_any_call("ETC", 10, "margin", "exchange")
882 @mock.patch.object(helper
, "prepare_trades")
883 @mock.patch.object(portfolio
.TradeStore
, "prepare_orders")
884 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
885 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
886 def test_print_orders(self
, log_stage
, fetch_balances
, prepare_orders
, prepare_trades
):
888 portfolio
.BalanceStore
.all
= {
889 "BTC": portfolio
.Balance("BTC", {
891 "exchange_total":"0.65",
892 "exchange_free": "0.35",
893 "exchange_used": "0.30"}),
894 "ETH": portfolio
.Balance("ETH", {
898 "exchange_used": 0}),
901 helper
.print_orders(market
)
902 fetch_balances
.assert_called_with(market
, tag
="print_orders")
903 prepare_trades
.assert_called_with(market
, base_currency
="BTC",
904 compute_value
="average", debug
=True)
905 prepare_orders
.assert_called_with(compute_value
="average")
906 log_stage
.assert_called_with("print_orders")
908 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
909 @mock.patch.object(portfolio
.BalanceStore
, "in_currency")
910 @mock.patch.object(helper
.ReportStore
, "print_log")
911 def test_print_balances(self
, print_log
, in_currency
, fetch_balances
):
913 portfolio
.BalanceStore
.all
= {
914 "BTC": portfolio
.Balance("BTC", {
916 "exchange_total":"0.65",
917 "exchange_free": "0.35",
918 "exchange_used": "0.30"}),
919 "ETH": portfolio
.Balance("ETH", {
923 "exchange_used": 0}),
925 in_currency
.return_value
= {
926 "BTC": portfolio
.Amount("BTC", "0.65"),
927 "ETH": portfolio
.Amount("BTC", "0.3"),
929 helper
.print_balances(market
)
930 fetch_balances
.assert_called_with(market
)
931 print_log
.assert_has_calls([
933 mock
.call(portfolio
.Amount("BTC", "0.95")),
936 @mock.patch.object(helper
, "prepare_trades")
937 @mock.patch.object(helper
, "follow_orders")
938 @mock.patch.object(portfolio
.TradeStore
, "prepare_orders")
939 @mock.patch.object(portfolio
.TradeStore
, "run_orders")
940 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
941 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
942 def test_process_sell_needed__1_sell(self
, log_stage
,
943 fetch_balances
, run_orders
, prepare_orders
, follow_orders
,
946 portfolio
.BalanceStore
.all
= {
947 "BTC": portfolio
.Balance("BTC", {
949 "exchange_total":"0.65",
950 "exchange_free": "0.35",
951 "exchange_used": "0.30"}),
952 "ETH": portfolio
.Balance("ETH", {
956 "exchange_used": 0}),
958 helper
.process_sell_needed__1_sell(market
)
959 fetch_balances
.assert_has_calls([
960 mock
.call(market
, tag
="process_sell_needed__1_sell_begin"),
961 mock
.call(market
, tag
="process_sell_needed__1_sell_end"),
963 prepare_trades
.assert_called_with(market
, base_currency
="BTC",
964 liquidity
="medium", debug
=False)
965 prepare_orders
.assert_called_with(compute_value
="average",
967 run_orders
.assert_called()
968 follow_orders
.assert_called()
969 log_stage
.assert_called_with("process_sell_needed__1_sell_end")
971 @mock.patch.object(helper
, "update_trades")
972 @mock.patch.object(helper
, "follow_orders")
973 @mock.patch.object(helper
, "move_balances")
974 @mock.patch.object(portfolio
.TradeStore
, "prepare_orders")
975 @mock.patch.object(portfolio
.TradeStore
, "run_orders")
976 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
977 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
978 def test_process_sell_needed__2_buy(self
, log_stage
, fetch_balances
,
979 run_orders
, prepare_orders
, move_balances
, follow_orders
,
982 portfolio
.BalanceStore
.all
= {
983 "BTC": portfolio
.Balance("BTC", {
985 "exchange_total":"0.65",
986 "exchange_free": "0.35",
987 "exchange_used": "0.30"}),
988 "ETH": portfolio
.Balance("ETH", {
992 "exchange_used": 0}),
994 helper
.process_sell_needed__2_buy(market
)
995 fetch_balances
.assert_has_calls([
996 mock
.call(market
, tag
="process_sell_needed__2_buy_begin"),
997 mock
.call(market
, tag
="process_sell_needed__2_buy_end"),
999 update_trades
.assert_called_with(market
, base_currency
="BTC",
1000 debug
=False, liquidity
="medium", only
="acquire")
1001 prepare_orders
.assert_called_with(compute_value
="average",
1003 move_balances
.assert_called_with(market
, debug
=False)
1004 run_orders
.assert_called()
1005 follow_orders
.assert_called()
1006 log_stage
.assert_called_with("process_sell_needed__2_buy_end")
1008 @mock.patch.object(helper
, "prepare_trades_to_sell_all")
1009 @mock.patch.object(helper
, "follow_orders")
1010 @mock.patch.object(portfolio
.TradeStore
, "prepare_orders")
1011 @mock.patch.object(portfolio
.TradeStore
, "run_orders")
1012 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
1013 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
1014 def test_process_sell_all__1_sell(self
, log_stage
, fetch_balances
,
1015 run_orders
, prepare_orders
, follow_orders
,
1016 prepare_trades_to_sell_all
):
1017 market
= mock
.Mock()
1018 portfolio
.BalanceStore
.all
= {
1019 "BTC": portfolio
.Balance("BTC", {
1021 "exchange_total":"0.65",
1022 "exchange_free": "0.35",
1023 "exchange_used": "0.30"}),
1024 "ETH": portfolio
.Balance("ETH", {
1026 "exchange_total": 3,
1028 "exchange_used": 0}),
1030 helper
.process_sell_all__1_all_sell(market
)
1031 fetch_balances
.assert_has_calls([
1032 mock
.call(market
, tag
="process_sell_all__1_all_sell_begin"),
1033 mock
.call(market
, tag
="process_sell_all__1_all_sell_end"),
1035 prepare_trades_to_sell_all
.assert_called_with(market
, base_currency
="BTC",
1037 prepare_orders
.assert_called_with(compute_value
="average")
1038 run_orders
.assert_called()
1039 follow_orders
.assert_called()
1040 log_stage
.assert_called_with("process_sell_all__1_all_sell_end")
1042 @mock.patch.object(helper
, "prepare_trades")
1043 @mock.patch.object(helper
, "follow_orders")
1044 @mock.patch.object(helper
, "move_balances")
1045 @mock.patch.object(portfolio
.TradeStore
, "prepare_orders")
1046 @mock.patch.object(portfolio
.TradeStore
, "run_orders")
1047 @mock.patch.object(portfolio
.BalanceStore
, "fetch_balances")
1048 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
1049 def test_process_sell_all__2_all_buy(self
, log_stage
,
1050 fetch_balances
, run_orders
, prepare_orders
, move_balances
,
1051 follow_orders
, prepare_trades
):
1052 market
= mock
.Mock()
1053 portfolio
.BalanceStore
.all
= {
1054 "BTC": portfolio
.Balance("BTC", {
1056 "exchange_total":"0.65",
1057 "exchange_free": "0.35",
1058 "exchange_used": "0.30"}),
1059 "ETH": portfolio
.Balance("ETH", {
1061 "exchange_total": 3,
1063 "exchange_used": 0}),
1065 helper
.process_sell_all__2_all_buy(market
)
1066 fetch_balances
.assert_has_calls([
1067 mock
.call(market
, tag
="process_sell_all__2_all_buy_begin"),
1068 mock
.call(market
, tag
="process_sell_all__2_all_buy_end"),
1070 prepare_trades
.assert_called_with(market
, base_currency
="BTC",
1071 liquidity
="medium", debug
=False)
1072 prepare_orders
.assert_called_with(compute_value
="average")
1073 move_balances
.assert_called_with(market
, debug
=False)
1074 run_orders
.assert_called()
1075 follow_orders
.assert_called()
1076 log_stage
.assert_called_with("process_sell_all__2_all_buy_end")
1078 def test_reset_all(self
):
1079 portfolio
.BalanceStore
.all
= { "foo": "bar" }
1080 portfolio
.ReportStore
.logs
.append("hey")
1081 portfolio
.TradeStore
.all
.append("bouh")
1085 self
.assertEqual(0, len(portfolio
.BalanceStore
.all
))
1086 self
.assertEqual(0, len(portfolio
.ReportStore
.logs
))
1087 self
.assertEqual(0, len(portfolio
.TradeStore
.all
))
1089 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1090 class TradeStoreTest(WebMockTestCase
):
1091 @mock.patch.object(portfolio
.BalanceStore
, "currencies")
1092 @mock.patch.object(portfolio
.TradeStore
, "trade_if_matching")
1093 @mock.patch.object(portfolio
.ReportStore
, "log_trades")
1094 def test_compute_trades(self
, log_trades
, trade_if_matching
, currencies
):
1095 currencies
.return_value
= ["XMR", "DASH", "XVG", "BTC", "ETH"]
1098 "XMR": portfolio
.Amount("BTC", D("0.9")),
1099 "DASH": portfolio
.Amount("BTC", D("0.4")),
1100 "XVG": portfolio
.Amount("BTC", D("-0.5")),
1101 "BTC": portfolio
.Amount("BTC", D("0.5")),
1104 "DASH": portfolio
.Amount("BTC", D("0.5")),
1105 "XVG": portfolio
.Amount("BTC", D("0.1")),
1106 "BTC": portfolio
.Amount("BTC", D("0.4")),
1107 "ETH": portfolio
.Amount("BTC", D("0.3")),
1116 trade_if_matching
.side_effect
= side_effect
1118 portfolio
.TradeStore
.compute_trades(values_in_base
,
1119 new_repartition
, only
="only", market
="market")
1121 self
.assertEqual(5, trade_if_matching
.call_count
)
1122 self
.assertEqual(3, len(portfolio
.TradeStore
.all
))
1123 self
.assertEqual([1, 4, 5], portfolio
.TradeStore
.all
)
1124 log_trades
.assert_called_with(side_effect
, "only", False)
1126 def test_trade_if_matching(self
):
1127 result
= portfolio
.TradeStore
.trade_if_matching(
1128 portfolio
.Amount("BTC", D("0")),
1129 portfolio
.Amount("BTC", D("0.3")),
1130 "ETH", only
="nope", market
="market"
1132 self
.assertEqual(False, result
[0])
1133 self
.assertIsInstance(result
[1], portfolio
.Trade
)
1135 portfolio
.TradeStore
.all
= []
1136 result
= portfolio
.TradeStore
.trade_if_matching(
1137 portfolio
.Amount("BTC", D("0")),
1138 portfolio
.Amount("BTC", D("0.3")),
1139 "ETH", only
=None, market
="market"
1141 self
.assertEqual(True, result
[0])
1143 portfolio
.TradeStore
.all
= []
1144 result
= portfolio
.TradeStore
.trade_if_matching(
1145 portfolio
.Amount("BTC", D("0")),
1146 portfolio
.Amount("BTC", D("0.3")),
1147 "ETH", only
="acquire", market
="market"
1149 self
.assertEqual(True, result
[0])
1151 portfolio
.TradeStore
.all
= []
1152 result
= portfolio
.TradeStore
.trade_if_matching(
1153 portfolio
.Amount("BTC", D("0")),
1154 portfolio
.Amount("BTC", D("0.3")),
1155 "ETH", only
="dispose", market
="market"
1157 self
.assertEqual(False, result
[0])
1159 @mock.patch.object(portfolio
.ReportStore
, "log_orders")
1160 def test_prepare_orders(self
, log_orders
):
1161 trade_mock1
= mock
.Mock()
1162 trade_mock2
= mock
.Mock()
1164 trade_mock1
.prepare_order
.return_value
= 1
1165 trade_mock2
.prepare_order
.return_value
= 2
1167 portfolio
.TradeStore
.all
.append(trade_mock1
)
1168 portfolio
.TradeStore
.all
.append(trade_mock2
)
1170 portfolio
.TradeStore
.prepare_orders()
1171 trade_mock1
.prepare_order
.assert_called_with(compute_value
="default")
1172 trade_mock2
.prepare_order
.assert_called_with(compute_value
="default")
1173 log_orders
.assert_called_once_with([1, 2], None, "default")
1175 log_orders
.reset_mock()
1177 portfolio
.TradeStore
.prepare_orders(compute_value
="bla")
1178 trade_mock1
.prepare_order
.assert_called_with(compute_value
="bla")
1179 trade_mock2
.prepare_order
.assert_called_with(compute_value
="bla")
1180 log_orders
.assert_called_once_with([1, 2], None, "bla")
1182 trade_mock1
.prepare_order
.reset_mock()
1183 trade_mock2
.prepare_order
.reset_mock()
1184 log_orders
.reset_mock()
1186 trade_mock1
.action
= "foo"
1187 trade_mock2
.action
= "bar"
1188 portfolio
.TradeStore
.prepare_orders(only
="bar")
1189 trade_mock1
.prepare_order
.assert_not_called()
1190 trade_mock2
.prepare_order
.assert_called_with(compute_value
="default")
1191 log_orders
.assert_called_once_with([2], "bar", "default")
1193 def test_print_all_with_order(self
):
1194 trade_mock1
= mock
.Mock()
1195 trade_mock2
= mock
.Mock()
1196 trade_mock3
= mock
.Mock()
1197 portfolio
.TradeStore
.all
= [trade_mock1
, trade_mock2
, trade_mock3
]
1199 portfolio
.TradeStore
.print_all_with_order()
1201 trade_mock1
.print_with_order
.assert_called()
1202 trade_mock2
.print_with_order
.assert_called()
1203 trade_mock3
.print_with_order
.assert_called()
1205 @mock.patch.object(portfolio
.ReportStore
, "log_stage")
1206 @mock.patch.object(portfolio
.ReportStore
, "log_orders")
1207 @mock.patch.object(portfolio
.TradeStore
, "all_orders")
1208 def test_run_orders(self
, all_orders
, log_orders
, log_stage
):
1209 order_mock1
= mock
.Mock()
1210 order_mock2
= mock
.Mock()
1211 order_mock3
= mock
.Mock()
1212 all_orders
.return_value
= [order_mock1
, order_mock2
, order_mock3
]
1213 portfolio
.TradeStore
.run_orders()
1214 all_orders
.assert_called_with(state
="pending")
1216 order_mock1
.run
.assert_called()
1217 order_mock2
.run
.assert_called()
1218 order_mock3
.run
.assert_called()
1220 log_stage
.assert_called_with("run_orders")
1221 log_orders
.assert_called_with([order_mock1
, order_mock2
,
1224 def test_all_orders(self
):
1225 trade_mock1
= mock
.Mock()
1226 trade_mock2
= mock
.Mock()
1228 order_mock1
= mock
.Mock()
1229 order_mock2
= mock
.Mock()
1230 order_mock3
= mock
.Mock()
1232 trade_mock1
.orders
= [order_mock1
, order_mock2
]
1233 trade_mock2
.orders
= [order_mock3
]
1235 order_mock1
.status
= "pending"
1236 order_mock2
.status
= "open"
1237 order_mock3
.status
= "open"
1239 portfolio
.TradeStore
.all
.append(trade_mock1
)
1240 portfolio
.TradeStore
.all
.append(trade_mock2
)
1242 orders
= portfolio
.TradeStore
.all_orders()
1243 self
.assertEqual(3, len(orders
))
1245 open_orders
= portfolio
.TradeStore
.all_orders(state
="open")
1246 self
.assertEqual(2, len(open_orders
))
1247 self
.assertEqual([order_mock2
, order_mock3
], open_orders
)
1249 @mock.patch.object(portfolio
.TradeStore
, "all_orders")
1250 def test_update_all_orders_status(self
, all_orders
):
1251 order_mock1
= mock
.Mock()
1252 order_mock2
= mock
.Mock()
1253 order_mock3
= mock
.Mock()
1254 all_orders
.return_value
= [order_mock1
, order_mock2
, order_mock3
]
1255 portfolio
.TradeStore
.update_all_orders_status()
1256 all_orders
.assert_called_with(state
="open")
1258 order_mock1
.get_status
.assert_called()
1259 order_mock2
.get_status
.assert_called()
1260 order_mock3
.get_status
.assert_called()
1263 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1264 class BalanceStoreTest(WebMockTestCase
):
1266 super(BalanceStoreTest
, self
).setUp()
1268 self
.fetch_balance
= {
1272 "exchange_total": 0,
1276 "exchange_free": D("6.0"),
1277 "exchange_used": D("1.2"),
1278 "exchange_total": D("7.2"),
1282 "exchange_free": 16,
1284 "exchange_total": 16,
1290 "exchange_total": 0,
1291 "margin_total": D("-1.0"),
1296 @mock.patch.object(helper
, "get_ticker")
1297 @mock.patch("portfolio.ReportStore.log_tickers")
1298 def test_in_currency(self
, log_tickers
, get_ticker
):
1299 portfolio
.BalanceStore
.all
= {
1300 "BTC": portfolio
.Balance("BTC", {
1302 "exchange_total":"0.65",
1303 "exchange_free": "0.35",
1304 "exchange_used": "0.30"}),
1305 "ETH": portfolio
.Balance("ETH", {
1307 "exchange_total": 3,
1309 "exchange_used": 0}),
1311 market
= mock
.Mock()
1312 get_ticker
.return_value
= {
1315 "average": D("0.1"),
1318 amounts
= portfolio
.BalanceStore
.in_currency("BTC", market
)
1319 self
.assertEqual("BTC", amounts
["ETH"].currency
)
1320 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
1321 self
.assertEqual(D("0.30"), amounts
["ETH"].value
)
1322 log_tickers
.assert_called_once_with(market
, amounts
, "BTC",
1324 log_tickers
.reset_mock()
1326 amounts
= portfolio
.BalanceStore
.in_currency("BTC", market
, compute_value
="bid")
1327 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
1328 self
.assertEqual(D("0.27"), amounts
["ETH"].value
)
1329 log_tickers
.assert_called_once_with(market
, amounts
, "BTC",
1331 log_tickers
.reset_mock()
1333 amounts
= portfolio
.BalanceStore
.in_currency("BTC", market
, compute_value
="bid", type="exchange_used")
1334 self
.assertEqual(D("0.30"), amounts
["BTC"].value
)
1335 self
.assertEqual(0, amounts
["ETH"].value
)
1336 log_tickers
.assert_called_once_with(market
, amounts
, "BTC",
1337 "bid", "exchange_used")
1338 log_tickers
.reset_mock()
1340 @mock.patch.object(portfolio
.ReportStore
, "log_balances")
1341 def test_fetch_balances(self
, log_balances
):
1342 market
= mock
.Mock()
1343 market
.fetch_all_balances
.return_value
= self
.fetch_balance
1345 portfolio
.BalanceStore
.fetch_balances(market
)
1346 self
.assertNotIn("ETC", portfolio
.BalanceStore
.currencies())
1347 self
.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio
.BalanceStore
.currencies()))
1349 portfolio
.BalanceStore
.all
["ETC"] = portfolio
.Balance("ETC", {
1350 "exchange_total": "1", "exchange_free": "0",
1351 "exchange_used": "1" })
1352 portfolio
.BalanceStore
.fetch_balances(market
, tag
="foo")
1353 self
.assertEqual(0, portfolio
.BalanceStore
.all
["ETC"].total
)
1354 self
.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio
.BalanceStore
.currencies()))
1355 log_balances
.assert_called_with(market
, tag
="foo")
1357 @mock.patch.object(portfolio
.Portfolio
, "repartition")
1358 @mock.patch.object(portfolio
.ReportStore
, "log_balances")
1359 @mock.patch("store.ReportStore.log_dispatch")
1360 def test_dispatch_assets(self
, log_dispatch
, log_balances
, repartition
):
1361 market
= mock
.Mock()
1362 market
.fetch_all_balances
.return_value
= self
.fetch_balance
1363 portfolio
.BalanceStore
.fetch_balances(market
)
1365 self
.assertNotIn("XEM", portfolio
.BalanceStore
.currencies())
1367 repartition_hash
= {
1368 "XEM": (D("0.75"), "long"),
1369 "BTC": (D("0.26"), "long"),
1370 "DASH": (D("0.10"), "short"),
1372 repartition
.return_value
= repartition_hash
1374 amounts
= portfolio
.BalanceStore
.dispatch_assets(portfolio
.Amount("BTC", "11.1"))
1375 repartition
.assert_called_with(liquidity
="medium")
1376 self
.assertIn("XEM", portfolio
.BalanceStore
.currencies())
1377 self
.assertEqual(D("2.6"), amounts
["BTC"].value
)
1378 self
.assertEqual(D("7.5"), amounts
["XEM"].value
)
1379 self
.assertEqual(D("-1.0"), amounts
["DASH"].value
)
1380 log_balances
.assert_called_with(market
, tag
=None)
1381 log_dispatch
.assert_called_once_with(portfolio
.Amount("BTC",
1382 "11.1"), amounts
, "medium", repartition_hash
)
1384 def test_currencies(self
):
1385 portfolio
.BalanceStore
.all
= {
1386 "BTC": portfolio
.Balance("BTC", {
1388 "exchange_total":"0.65",
1389 "exchange_free": "0.35",
1390 "exchange_used": "0.30"}),
1391 "ETH": portfolio
.Balance("ETH", {
1393 "exchange_total": 3,
1395 "exchange_used": 0}),
1397 self
.assertListEqual(["BTC", "ETH"], list(portfolio
.BalanceStore
.currencies()))
1399 def test_as_json(self
):
1400 balance_mock1
= mock
.Mock()
1401 balance_mock1
.as_json
.return_value
= 1
1403 balance_mock2
= mock
.Mock()
1404 balance_mock2
.as_json
.return_value
= 2
1406 portfolio
.BalanceStore
.all
= {
1407 "BTC": balance_mock1
,
1408 "ETH": balance_mock2
,
1411 as_json
= portfolio
.BalanceStore
.as_json()
1412 self
.assertEqual(1, as_json
["BTC"])
1413 self
.assertEqual(2, as_json
["ETH"])
1416 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1417 class ComputationTest(WebMockTestCase
):
1418 def test_compute_value(self
):
1419 compute
= mock
.Mock()
1420 portfolio
.Computation
.compute_value("foo", "buy", compute_value
=compute
)
1421 compute
.assert_called_with("foo", "ask")
1423 compute
.reset_mock()
1424 portfolio
.Computation
.compute_value("foo", "sell", compute_value
=compute
)
1425 compute
.assert_called_with("foo", "bid")
1427 compute
.reset_mock()
1428 portfolio
.Computation
.compute_value("foo", "ask", compute_value
=compute
)
1429 compute
.assert_called_with("foo", "ask")
1431 compute
.reset_mock()
1432 portfolio
.Computation
.compute_value("foo", "bid", compute_value
=compute
)
1433 compute
.assert_called_with("foo", "bid")
1435 compute
.reset_mock()
1436 portfolio
.Computation
.computations
["test"] = compute
1437 portfolio
.Computation
.compute_value("foo", "bid", compute_value
="test")
1438 compute
.assert_called_with("foo", "bid")
1441 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1442 class TradeTest(WebMockTestCase
):
1444 def test_values_assertion(self
):
1445 value_from
= portfolio
.Amount("BTC", "1.0")
1446 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1447 value_to
= portfolio
.Amount("BTC", "1.0")
1448 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1449 self
.assertEqual("BTC", trade
.base_currency
)
1450 self
.assertEqual("ETH", trade
.currency
)
1452 with self
.assertRaises(AssertionError):
1453 portfolio
.Trade(value_from
, value_to
, "ETC")
1454 with self
.assertRaises(AssertionError):
1455 value_from
.linked_to
= None
1456 portfolio
.Trade(value_from
, value_to
, "ETH")
1457 with self
.assertRaises(AssertionError):
1458 value_from
.currency
= "ETH"
1459 portfolio
.Trade(value_from
, value_to
, "ETH")
1461 value_from
= portfolio
.Amount("BTC", 0)
1462 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1463 self
.assertEqual(0, trade
.value_from
.linked_to
)
1465 def test_action(self
):
1466 value_from
= portfolio
.Amount("BTC", "1.0")
1467 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1468 value_to
= portfolio
.Amount("BTC", "1.0")
1469 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1471 self
.assertIsNone(trade
.action
)
1473 value_from
= portfolio
.Amount("BTC", "1.0")
1474 value_from
.linked_to
= portfolio
.Amount("BTC", "1.0")
1475 value_to
= portfolio
.Amount("BTC", "2.0")
1476 trade
= portfolio
.Trade(value_from
, value_to
, "BTC")
1478 self
.assertIsNone(trade
.action
)
1480 value_from
= portfolio
.Amount("BTC", "0.5")
1481 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1482 value_to
= portfolio
.Amount("BTC", "1.0")
1483 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1485 self
.assertEqual("acquire", trade
.action
)
1487 value_from
= portfolio
.Amount("BTC", "0")
1488 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1489 value_to
= portfolio
.Amount("BTC", "-1.0")
1490 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1492 self
.assertEqual("acquire", trade
.action
)
1494 def test_order_action(self
):
1495 value_from
= portfolio
.Amount("BTC", "0.5")
1496 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1497 value_to
= portfolio
.Amount("BTC", "1.0")
1498 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1500 self
.assertEqual("buy", trade
.order_action(False))
1501 self
.assertEqual("sell", trade
.order_action(True))
1503 value_from
= portfolio
.Amount("BTC", "0")
1504 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1505 value_to
= portfolio
.Amount("BTC", "-1.0")
1506 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1508 self
.assertEqual("sell", trade
.order_action(False))
1509 self
.assertEqual("buy", trade
.order_action(True))
1511 def test_trade_type(self
):
1512 value_from
= portfolio
.Amount("BTC", "0.5")
1513 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1514 value_to
= portfolio
.Amount("BTC", "1.0")
1515 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1517 self
.assertEqual("long", trade
.trade_type
)
1519 value_from
= portfolio
.Amount("BTC", "0")
1520 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1521 value_to
= portfolio
.Amount("BTC", "-1.0")
1522 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1524 self
.assertEqual("short", trade
.trade_type
)
1526 def test_filled_amount(self
):
1527 value_from
= portfolio
.Amount("BTC", "0.5")
1528 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1529 value_to
= portfolio
.Amount("BTC", "1.0")
1530 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1532 order1
= mock
.Mock()
1533 order1
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.3")
1535 order2
= mock
.Mock()
1536 order2
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.01")
1537 trade
.orders
.append(order1
)
1538 trade
.orders
.append(order2
)
1540 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount())
1541 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
1542 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
1544 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=False))
1545 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
1546 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
1548 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=True))
1549 order1
.filled_amount
.assert_called_with(in_base_currency
=True)
1550 order2
.filled_amount
.assert_called_with(in_base_currency
=True)
1552 @mock.patch.object(helper
, "get_ticker")
1553 @mock.patch.object(portfolio
.Computation
, "compute_value")
1554 @mock.patch.object(portfolio
.Trade
, "filled_amount")
1555 @mock.patch.object(portfolio
, "Order")
1556 def test_prepare_order(self
, Order
, filled_amount
, compute_value
, get_ticker
):
1557 Order
.return_value
= "Order"
1559 with self
.subTest(desc
="Nothing to do"):
1560 value_from
= portfolio
.Amount("BTC", "10")
1561 value_from
.rate
= D("0.1")
1562 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1563 value_to
= portfolio
.Amount("BTC", "10")
1564 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1566 trade
.prepare_order()
1568 filled_amount
.assert_not_called()
1569 compute_value
.assert_not_called()
1570 self
.assertEqual(0, len(trade
.orders
))
1571 Order
.assert_not_called()
1573 get_ticker
.return_value
= { "inverted": False }
1574 with self
.subTest(desc
="Already filled"),\
1575 mock
.patch("portfolio.ReportStore") as report_store
:
1576 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
1577 compute_value
.return_value
= D("0.125")
1579 value_from
= portfolio
.Amount("BTC", "10")
1580 value_from
.rate
= D("0.1")
1581 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1582 value_to
= portfolio
.Amount("BTC", "0")
1583 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1585 trade
.prepare_order()
1587 filled_amount
.assert_called_with(in_base_currency
=False)
1588 compute_value
.assert_called_with(get_ticker
.return_value
, "sell", compute_value
="default")
1589 self
.assertEqual(0, len(trade
.orders
))
1590 report_store
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
1591 Order
.assert_not_called()
1593 with self
.subTest(action
="dispose", inverted
=False):
1594 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
1595 compute_value
.return_value
= D("0.125")
1597 value_from
= portfolio
.Amount("BTC", "10")
1598 value_from
.rate
= D("0.1")
1599 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1600 value_to
= portfolio
.Amount("BTC", "1")
1601 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1603 trade
.prepare_order()
1605 filled_amount
.assert_called_with(in_base_currency
=False)
1606 compute_value
.assert_called_with(get_ticker
.return_value
, "sell", compute_value
="default")
1607 self
.assertEqual(1, len(trade
.orders
))
1608 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
1609 D("0.125"), "BTC", "long", "market",
1610 trade
, close_if_possible
=False)
1612 with self
.subTest(action
="acquire", inverted
=False):
1613 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
1614 compute_value
.return_value
= D("0.125")
1616 value_from
= portfolio
.Amount("BTC", "1")
1617 value_from
.rate
= D("0.1")
1618 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
1619 value_to
= portfolio
.Amount("BTC", "10")
1620 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1622 trade
.prepare_order()
1624 filled_amount
.assert_called_with(in_base_currency
=True)
1625 compute_value
.assert_called_with(get_ticker
.return_value
, "buy", compute_value
="default")
1626 self
.assertEqual(1, len(trade
.orders
))
1628 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
1629 D("0.125"), "BTC", "long", "market",
1630 trade
, close_if_possible
=False)
1632 with self
.subTest(close_if_possible
=True):
1633 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
1634 compute_value
.return_value
= D("0.125")
1636 value_from
= portfolio
.Amount("BTC", "10")
1637 value_from
.rate
= D("0.1")
1638 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1639 value_to
= portfolio
.Amount("BTC", "0")
1640 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1642 trade
.prepare_order()
1644 filled_amount
.assert_called_with(in_base_currency
=False)
1645 compute_value
.assert_called_with(get_ticker
.return_value
, "sell", compute_value
="default")
1646 self
.assertEqual(1, len(trade
.orders
))
1647 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
1648 D("0.125"), "BTC", "long", "market",
1649 trade
, close_if_possible
=True)
1651 get_ticker
.return_value
= { "inverted": True, "original": {}
}
1652 with self
.subTest(action
="dispose", inverted
=True):
1653 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
1654 compute_value
.return_value
= D("125")
1656 value_from
= portfolio
.Amount("BTC", "10")
1657 value_from
.rate
= D("0.01")
1658 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
1659 value_to
= portfolio
.Amount("BTC", "1")
1660 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1662 trade
.prepare_order(compute_value
="foo")
1664 filled_amount
.assert_called_with(in_base_currency
=True)
1665 compute_value
.assert_called_with(get_ticker
.return_value
["original"], "buy", compute_value
="foo")
1666 self
.assertEqual(1, len(trade
.orders
))
1667 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
1668 D("125"), "FOO", "long", "market",
1669 trade
, close_if_possible
=False)
1671 with self
.subTest(action
="acquire", inverted
=True):
1672 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
1673 compute_value
.return_value
= D("125")
1675 value_from
= portfolio
.Amount("BTC", "1")
1676 value_from
.rate
= D("0.01")
1677 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1678 value_to
= portfolio
.Amount("BTC", "10")
1679 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", market
="market")
1681 trade
.prepare_order(compute_value
="foo")
1683 filled_amount
.assert_called_with(in_base_currency
=False)
1684 compute_value
.assert_called_with(get_ticker
.return_value
["original"], "sell", compute_value
="foo")
1685 self
.assertEqual(1, len(trade
.orders
))
1686 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
1687 D("125"), "FOO", "long", "market",
1688 trade
, close_if_possible
=False)
1691 @mock.patch.object(portfolio
.Trade
, "prepare_order")
1692 def test_update_order(self
, prepare_order
):
1693 order_mock
= mock
.Mock()
1694 new_order_mock
= mock
.Mock()
1696 value_from
= portfolio
.Amount("BTC", "0.5")
1697 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1698 value_to
= portfolio
.Amount("BTC", "1.0")
1699 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1700 prepare_order
.return_value
= new_order_mock
1702 for i
in [0, 1, 3, 4, 6]:
1703 with self
.subTest(tick
=i
),\
1704 mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1705 trade
.update_order(order_mock
, i
)
1706 order_mock
.cancel
.assert_not_called()
1707 new_order_mock
.run
.assert_not_called()
1708 log_order
.assert_called_once_with(order_mock
, i
,
1709 update
="waiting", compute_value
=None, new_order
=None)
1711 order_mock
.reset_mock()
1712 new_order_mock
.reset_mock()
1715 with mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1716 trade
.update_order(order_mock
, 2)
1717 order_mock
.cancel
.assert_called()
1718 new_order_mock
.run
.assert_called()
1719 prepare_order
.assert_called()
1720 log_order
.assert_called()
1721 self
.assertEqual(2, log_order
.call_count
)
1723 mock
.call(order_mock
, 2, update
="adjusting",
1724 compute_value
='lambda x, y: (x[y] + x["average"]) / 2',
1725 new_order
=new_order_mock
),
1726 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
1728 log_order
.assert_has_calls(calls
)
1730 order_mock
.reset_mock()
1731 new_order_mock
.reset_mock()
1734 with mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1735 trade
.update_order(order_mock
, 5)
1736 order_mock
.cancel
.assert_called()
1737 new_order_mock
.run
.assert_called()
1738 prepare_order
.assert_called()
1739 self
.assertEqual(2, log_order
.call_count
)
1740 log_order
.assert_called()
1742 mock
.call(order_mock
, 5, update
="adjusting",
1743 compute_value
='lambda x, y: (x[y]*2 + x["average"]) / 3',
1744 new_order
=new_order_mock
),
1745 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
1747 log_order
.assert_has_calls(calls
)
1749 order_mock
.reset_mock()
1750 new_order_mock
.reset_mock()
1753 with mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1754 trade
.update_order(order_mock
, 7)
1755 order_mock
.cancel
.assert_called()
1756 new_order_mock
.run
.assert_called()
1757 prepare_order
.assert_called_with(compute_value
="default")
1758 log_order
.assert_called()
1759 self
.assertEqual(2, log_order
.call_count
)
1761 mock
.call(order_mock
, 7, update
="market_fallback",
1762 compute_value
='default',
1763 new_order
=new_order_mock
),
1764 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
1766 log_order
.assert_has_calls(calls
)
1768 order_mock
.reset_mock()
1769 new_order_mock
.reset_mock()
1772 for i
in [10, 13, 16]:
1773 with self
.subTest(tick
=i
), mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1774 trade
.update_order(order_mock
, i
)
1775 order_mock
.cancel
.assert_called()
1776 new_order_mock
.run
.assert_called()
1777 prepare_order
.assert_called_with(compute_value
="default")
1778 log_order
.assert_called()
1779 self
.assertEqual(2, log_order
.call_count
)
1781 mock
.call(order_mock
, i
, update
="market_adjust",
1782 compute_value
='default',
1783 new_order
=new_order_mock
),
1784 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
1786 log_order
.assert_has_calls(calls
)
1788 order_mock
.reset_mock()
1789 new_order_mock
.reset_mock()
1792 for i
in [8, 9, 11, 12]:
1793 with self
.subTest(tick
=i
), mock
.patch
.object(portfolio
.ReportStore
, "log_order") as log_order
:
1794 trade
.update_order(order_mock
, i
)
1795 order_mock
.cancel
.assert_not_called()
1796 new_order_mock
.run
.assert_not_called()
1797 log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
1798 compute_value
=None, new_order
=None)
1800 order_mock
.reset_mock()
1801 new_order_mock
.reset_mock()
1805 @mock.patch.object(portfolio
.ReportStore
, "print_log")
1806 def test_print_with_order(self
, print_log
):
1807 value_from
= portfolio
.Amount("BTC", "0.5")
1808 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1809 value_to
= portfolio
.Amount("BTC", "1.0")
1810 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1812 order_mock1
= mock
.Mock()
1813 order_mock1
.__repr__
= mock
.Mock()
1814 order_mock1
.__repr__
.return_value
= "Mock 1"
1815 order_mock2
= mock
.Mock()
1816 order_mock2
.__repr__
= mock
.Mock()
1817 order_mock2
.__repr__
.return_value
= "Mock 2"
1818 order_mock1
.mouvements
= []
1819 mouvement_mock1
= mock
.Mock()
1820 mouvement_mock1
.__repr__
= mock
.Mock()
1821 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
1822 mouvement_mock2
= mock
.Mock()
1823 mouvement_mock2
.__repr__
= mock
.Mock()
1824 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
1825 order_mock2
.mouvements
= [
1826 mouvement_mock1
, mouvement_mock2
1828 trade
.orders
.append(order_mock1
)
1829 trade
.orders
.append(order_mock2
)
1831 trade
.print_with_order()
1833 print_log
.assert_called()
1834 calls
= print_log
.mock_calls
1835 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
1836 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
1837 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
1838 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
1839 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
1841 def test__repr(self
):
1842 value_from
= portfolio
.Amount("BTC", "0.5")
1843 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1844 value_to
= portfolio
.Amount("BTC", "1.0")
1845 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1847 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
1849 def test_as_json(self
):
1850 value_from
= portfolio
.Amount("BTC", "0.5")
1851 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1852 value_to
= portfolio
.Amount("BTC", "1.0")
1853 trade
= portfolio
.Trade(value_from
, value_to
, "ETH")
1855 as_json
= trade
.as_json()
1856 self
.assertEqual("acquire", as_json
["action"])
1857 self
.assertEqual(D("0.5"), as_json
["from"])
1858 self
.assertEqual(D("1.0"), as_json
["to"])
1859 self
.assertEqual("ETH", as_json
["currency"])
1860 self
.assertEqual("BTC", as_json
["base_currency"])
1862 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1863 class OrderTest(WebMockTestCase
):
1864 def test_values(self
):
1865 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1866 D("0.1"), "BTC", "long", "market", "trade")
1867 self
.assertEqual("buy", order
.action
)
1868 self
.assertEqual(10, order
.amount
.value
)
1869 self
.assertEqual("ETH", order
.amount
.currency
)
1870 self
.assertEqual(D("0.1"), order
.rate
)
1871 self
.assertEqual("BTC", order
.base_currency
)
1872 self
.assertEqual("market", order
.market
)
1873 self
.assertEqual("long", order
.trade_type
)
1874 self
.assertEqual("pending", order
.status
)
1875 self
.assertEqual("trade", order
.trade
)
1876 self
.assertIsNone(order
.id)
1877 self
.assertFalse(order
.close_if_possible
)
1879 def test__repr(self
):
1880 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1881 D("0.1"), "BTC", "long", "market", "trade")
1882 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
1884 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1885 D("0.1"), "BTC", "long", "market", "trade",
1886 close_if_possible
=True)
1887 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
1889 def test_as_json(self
):
1890 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1891 D("0.1"), "BTC", "long", "market", "trade")
1892 mouvement_mock1
= mock
.Mock()
1893 mouvement_mock1
.as_json
.return_value
= 1
1894 mouvement_mock2
= mock
.Mock()
1895 mouvement_mock2
.as_json
.return_value
= 2
1897 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
1898 as_json
= order
.as_json()
1899 self
.assertEqual("buy", as_json
["action"])
1900 self
.assertEqual("long", as_json
["trade_type"])
1901 self
.assertEqual(10, as_json
["amount"])
1902 self
.assertEqual("ETH", as_json
["currency"])
1903 self
.assertEqual("BTC", as_json
["base_currency"])
1904 self
.assertEqual(D("0.1"), as_json
["rate"])
1905 self
.assertEqual("pending", as_json
["status"])
1906 self
.assertEqual(False, as_json
["close_if_possible"])
1907 self
.assertIsNone(as_json
["id"])
1908 self
.assertEqual([1, 2], as_json
["mouvements"])
1910 def test_account(self
):
1911 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1912 D("0.1"), "BTC", "long", "market", "trade")
1913 self
.assertEqual("exchange", order
.account
)
1915 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
1916 D("0.1"), "BTC", "short", "market", "trade")
1917 self
.assertEqual("margin", order
.account
)
1919 def test_pending(self
):
1920 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1921 D("0.1"), "BTC", "long", "market", "trade")
1922 self
.assertTrue(order
.pending
)
1923 order
.status
= "open"
1924 self
.assertFalse(order
.pending
)
1926 def test_open(self
):
1927 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1928 D("0.1"), "BTC", "long", "market", "trade")
1929 self
.assertFalse(order
.open)
1930 order
.status
= "open"
1931 self
.assertTrue(order
.open)
1933 def test_finished(self
):
1934 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1935 D("0.1"), "BTC", "long", "market", "trade")
1936 self
.assertFalse(order
.finished
)
1937 order
.status
= "closed"
1938 self
.assertTrue(order
.finished
)
1939 order
.status
= "canceled"
1940 self
.assertTrue(order
.finished
)
1941 order
.status
= "error"
1942 self
.assertTrue(order
.finished
)
1944 @mock.patch.object(portfolio
.Order
, "fetch")
1945 @mock.patch("portfolio.ReportStore")
1946 def test_cancel(self
, report_store
, fetch
):
1947 market
= mock
.Mock()
1948 portfolio
.TradeStore
.debug
= True
1949 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1950 D("0.1"), "BTC", "long", market
, "trade")
1951 order
.status
= "open"
1954 market
.cancel_order
.assert_not_called()
1955 report_store
.log_debug_action
.assert_called_once()
1956 report_store
.log_debug_action
.reset_mock()
1957 self
.assertEqual("canceled", order
.status
)
1959 portfolio
.TradeStore
.debug
= False
1960 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1961 D("0.1"), "BTC", "long", market
, "trade")
1962 order
.status
= "open"
1966 market
.cancel_order
.assert_called_with(42)
1967 fetch
.assert_called_once()
1968 report_store
.log_debug_action
.assert_not_called()
1970 def test_dust_amount_remaining(self
):
1971 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1972 D("0.1"), "BTC", "long", "market", "trade")
1973 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", 1))
1974 self
.assertFalse(order
.dust_amount_remaining())
1976 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", D("0.0001")))
1977 self
.assertTrue(order
.dust_amount_remaining())
1979 @mock.patch.object(portfolio
.Order
, "fetch")
1980 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
1981 def test_remaining_amount(self
, filled_amount
, fetch
):
1982 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1983 D("0.1"), "BTC", "long", "market", "trade")
1985 self
.assertEqual(9, order
.remaining_amount().value
)
1986 order
.fetch
.assert_not_called()
1988 order
.status
= "open"
1989 self
.assertEqual(9, order
.remaining_amount().value
)
1990 fetch
.assert_called_once()
1992 @mock.patch.object(portfolio
.Order
, "fetch")
1993 def test_filled_amount(self
, fetch
):
1994 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
1995 D("0.1"), "BTC", "long", "market", "trade")
1996 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
1997 "tradeID": 42, "type": "buy", "fee": "0.0015",
1998 "date": "2017-12-30 12:00:12", "rate": "0.1",
1999 "amount": "3", "total": "0.3"
2001 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
2002 "tradeID": 43, "type": "buy", "fee": "0.0015",
2003 "date": "2017-12-30 13:00:12", "rate": "0.2",
2004 "amount": "2", "total": "0.4"
2006 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
2007 fetch
.assert_not_called()
2008 order
.status
= "open"
2009 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
2010 fetch
.assert_called_once()
2011 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
2013 def test_fetch_mouvements(self
):
2014 market
= mock
.Mock()
2015 market
.privatePostReturnOrderTrades
.return_value
= [
2017 "tradeID": 42, "type": "buy", "fee": "0.0015",
2018 "date": "2017-12-30 12:00:12", "rate": "0.1",
2019 "amount": "3", "total": "0.3"
2022 "tradeID": 43, "type": "buy", "fee": "0.0015",
2023 "date": "2017-12-30 13:00:12", "rate": "0.2",
2024 "amount": "2", "total": "0.4"
2027 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2028 D("0.1"), "BTC", "long", market
, "trade")
2030 order
.mouvements
= ["Foo", "Bar", "Baz"]
2032 order
.fetch_mouvements()
2034 market
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
2035 self
.assertEqual(2, len(order
.mouvements
))
2036 self
.assertEqual(42, order
.mouvements
[0].id)
2037 self
.assertEqual(43, order
.mouvements
[1].id)
2039 market
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
2040 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2041 D("0.1"), "BTC", "long", market
, "trade")
2042 order
.fetch_mouvements()
2043 self
.assertEqual(0, len(order
.mouvements
))
2045 @mock.patch("portfolio.ReportStore")
2046 def test_mark_finished_order(self
, report_store
):
2047 market
= mock
.Mock()
2048 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2049 D("0.1"), "BTC", "short", market
, "trade",
2050 close_if_possible
=True)
2051 order
.status
= "closed"
2052 portfolio
.TradeStore
.debug
= False
2054 order
.mark_finished_order()
2055 market
.close_margin_position
.assert_called_with("ETH", "BTC")
2056 market
.close_margin_position
.reset_mock()
2058 order
.status
= "open"
2059 order
.mark_finished_order()
2060 market
.close_margin_position
.assert_not_called()
2062 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2063 D("0.1"), "BTC", "short", market
, "trade",
2064 close_if_possible
=False)
2065 order
.status
= "closed"
2066 order
.mark_finished_order()
2067 market
.close_margin_position
.assert_not_called()
2069 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
2070 D("0.1"), "BTC", "short", market
, "trade",
2071 close_if_possible
=True)
2072 order
.status
= "closed"
2073 order
.mark_finished_order()
2074 market
.close_margin_position
.assert_not_called()
2076 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2077 D("0.1"), "BTC", "long", market
, "trade",
2078 close_if_possible
=True)
2079 order
.status
= "closed"
2080 order
.mark_finished_order()
2081 market
.close_margin_position
.assert_not_called()
2083 portfolio
.TradeStore
.debug
= True
2085 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2086 D("0.1"), "BTC", "short", market
, "trade",
2087 close_if_possible
=True)
2088 order
.status
= "closed"
2090 order
.mark_finished_order()
2091 market
.close_margin_position
.assert_not_called()
2092 report_store
.log_debug_action
.assert_called_once()
2094 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
2095 @mock.patch("portfolio.ReportStore")
2096 def test_fetch(self
, report_store
, fetch_mouvements
):
2097 time
= self
.time
.time()
2098 with mock
.patch
.object(portfolio
.time
, "time") as time_mock
:
2099 market
= mock
.Mock()
2100 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2101 D("0.1"), "BTC", "long", market
, "trade")
2103 with self
.subTest(debug
=True):
2104 portfolio
.TradeStore
.debug
= True
2106 time_mock
.assert_not_called()
2107 report_store
.log_debug_action
.assert_called_once()
2108 report_store
.log_debug_action
.reset_mock()
2109 order
.fetch(force
=True)
2110 time_mock
.assert_not_called()
2111 market
.fetch_order
.assert_not_called()
2112 fetch_mouvements
.assert_not_called()
2113 report_store
.log_debug_action
.assert_called_once()
2114 report_store
.log_debug_action
.reset_mock()
2115 self
.assertIsNone(order
.fetch_cache_timestamp
)
2117 with self
.subTest(debug
=False):
2118 portfolio
.TradeStore
.debug
= False
2119 time_mock
.return_value
= time
2120 market
.fetch_order
.return_value
= {
2122 "datetime": "timestamp"
2126 market
.fetch_order
.assert_called_once()
2127 fetch_mouvements
.assert_called_once()
2128 self
.assertEqual("foo", order
.status
)
2129 self
.assertEqual("timestamp", order
.timestamp
)
2130 self
.assertEqual(time
, order
.fetch_cache_timestamp
)
2131 self
.assertEqual(1, len(order
.results
))
2133 market
.fetch_order
.reset_mock()
2134 fetch_mouvements
.reset_mock()
2136 time_mock
.return_value
= time
+ 8
2138 market
.fetch_order
.assert_not_called()
2139 fetch_mouvements
.assert_not_called()
2141 order
.fetch(force
=True)
2142 market
.fetch_order
.assert_called_once()
2143 fetch_mouvements
.assert_called_once()
2145 market
.fetch_order
.reset_mock()
2146 fetch_mouvements
.reset_mock()
2148 time_mock
.return_value
= time
+ 19
2150 market
.fetch_order
.assert_called_once()
2151 fetch_mouvements
.assert_called_once()
2152 report_store
.log_debug_action
.assert_not_called()
2154 @mock.patch.object(portfolio
.Order
, "fetch")
2155 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
2156 @mock.patch("portfolio.ReportStore")
2157 def test_get_status(self
, report_store
, mark_finished_order
, fetch
):
2158 with self
.subTest(debug
=True):
2159 portfolio
.TradeStore
.debug
= True
2160 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2161 D("0.1"), "BTC", "long", "market", "trade")
2162 self
.assertEqual("pending", order
.get_status())
2163 fetch
.assert_not_called()
2164 report_store
.log_debug_action
.assert_called_once()
2166 with self
.subTest(debug
=False, finished
=False):
2167 portfolio
.TradeStore
.debug
= False
2168 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2169 D("0.1"), "BTC", "long", "market", "trade")
2171 def update_status():
2172 order
.status
= "open"
2173 return update_status
2174 fetch
.side_effect
= _fetch(order
)
2175 self
.assertEqual("open", order
.get_status())
2176 mark_finished_order
.assert_not_called()
2177 fetch
.assert_called_once()
2179 mark_finished_order
.reset_mock()
2181 with self
.subTest(debug
=False, finished
=True):
2182 portfolio
.TradeStore
.debug
= False
2183 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2184 D("0.1"), "BTC", "long", "market", "trade")
2186 def update_status():
2187 order
.status
= "closed"
2188 return update_status
2189 fetch
.side_effect
= _fetch(order
)
2190 self
.assertEqual("closed", order
.get_status())
2191 mark_finished_order
.assert_called_once()
2192 fetch
.assert_called_once()
2195 market
= mock
.Mock()
2197 market
.order_precision
.return_value
= 4
2198 with self
.subTest(debug
=True),\
2199 mock
.patch('portfolio.ReportStore') as report_store
:
2200 portfolio
.TradeStore
.debug
= True
2201 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2202 D("0.1"), "BTC", "long", market
, "trade")
2204 market
.create_order
.assert_not_called()
2205 report_store
.log_debug_action
.assert_called_with("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
2206 self
.assertEqual("open", order
.status
)
2207 self
.assertEqual(1, len(order
.results
))
2208 self
.assertEqual(-1, order
.id)
2210 market
.create_order
.reset_mock()
2211 with self
.subTest(debug
=False):
2212 portfolio
.TradeStore
.debug
= False
2213 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2214 D("0.1"), "BTC", "long", market
, "trade")
2215 market
.create_order
.return_value
= { "id": 123 }
2217 market
.create_order
.assert_called_once()
2218 self
.assertEqual(1, len(order
.results
))
2219 self
.assertEqual("open", order
.status
)
2221 market
.create_order
.reset_mock()
2222 with self
.subTest(exception
=True),\
2223 mock
.patch('portfolio.ReportStore') as report_store
:
2224 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2225 D("0.1"), "BTC", "long", market
, "trade")
2226 market
.create_order
.side_effect
= Exception("bouh")
2228 market
.create_order
.assert_called_once()
2229 self
.assertEqual(0, len(order
.results
))
2230 self
.assertEqual("error", order
.status
)
2231 report_store
.log_error
.assert_called_once()
2233 market
.create_order
.reset_mock()
2234 with self
.subTest(dust_amount_exception
=True),\
2235 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
2236 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
2237 D("0.1"), "BTC", "long", market
, "trade")
2238 market
.create_order
.side_effect
= portfolio
.ExchangeNotAvailable
2240 market
.create_order
.assert_called_once()
2241 self
.assertEqual(0, len(order
.results
))
2242 self
.assertEqual("closed", order
.status
)
2243 mark_finished_order
.assert_called_once()
2246 @unittest.skipUnless("unit" in limits
, "Unit skipped")
2247 class MouvementTest(WebMockTestCase
):
2248 def test_values(self
):
2249 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2250 "tradeID": 42, "type": "buy", "fee": "0.0015",
2251 "date": "2017-12-30 12:00:12", "rate": "0.1",
2252 "amount": "10", "total": "1"
2254 self
.assertEqual("ETH", mouvement
.currency
)
2255 self
.assertEqual("BTC", mouvement
.base_currency
)
2256 self
.assertEqual(42, mouvement
.id)
2257 self
.assertEqual("buy", mouvement
.action
)
2258 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
2259 self
.assertEqual(portfolio
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
2260 self
.assertEqual(D("0.1"), mouvement
.rate
)
2261 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
2262 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
2264 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
2265 self
.assertIsNone(mouvement
.date
)
2266 self
.assertIsNone(mouvement
.id)
2267 self
.assertIsNone(mouvement
.action
)
2268 self
.assertEqual(-1, mouvement
.fee_rate
)
2269 self
.assertEqual(0, mouvement
.rate
)
2270 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
2271 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
2273 def test__repr(self
):
2274 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2275 "tradeID": 42, "type": "buy", "fee": "0.0015",
2276 "date": "2017-12-30 12:00:12", "rate": "0.1",
2277 "amount": "10", "total": "1"
2279 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
2281 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2282 "tradeID": 42, "type": "buy",
2283 "date": "garbage", "rate": "0.1",
2284 "amount": "10", "total": "1"
2286 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
2288 def test_as_json(self
):
2289 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2290 "tradeID": 42, "type": "buy", "fee": "0.0015",
2291 "date": "2017-12-30 12:00:12", "rate": "0.1",
2292 "amount": "10", "total": "1"
2294 as_json
= mouvement
.as_json()
2296 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
2297 self
.assertEqual(portfolio
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
2298 self
.assertEqual("buy", as_json
["action"])
2299 self
.assertEqual(D("10"), as_json
["total"])
2300 self
.assertEqual(D("1"), as_json
["total_in_base"])
2301 self
.assertEqual("BTC", as_json
["base_currency"])
2302 self
.assertEqual("ETH", as_json
["currency"])
2304 @unittest.skipUnless("unit" in limits
, "Unit skipped")
2305 class ReportStoreTest(WebMockTestCase
):
2306 def test_add_log(self
):
2307 portfolio
.ReportStore
.add_log({"foo": "bar"}
)
2309 self
.assertEqual({"foo": "bar", "date": mock.ANY}
, portfolio
.ReportStore
.logs
[0])
2311 def test_set_verbose(self
):
2312 with self
.subTest(verbose
=True):
2313 portfolio
.ReportStore
.set_verbose(True)
2314 self
.assertTrue(portfolio
.ReportStore
.verbose_print
)
2316 with self
.subTest(verbose
=False):
2317 portfolio
.ReportStore
.set_verbose(False)
2318 self
.assertFalse(portfolio
.ReportStore
.verbose_print
)
2320 def test_print_log(self
):
2321 with self
.subTest(verbose
=True),\
2322 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
2323 portfolio
.ReportStore
.set_verbose(True)
2324 portfolio
.ReportStore
.print_log("Coucou")
2325 portfolio
.ReportStore
.print_log(portfolio
.Amount("BTC", 1))
2326 self
.assertEqual(stdout_mock
.getvalue(), "Coucou\n1.00000000 BTC\n")
2328 with self
.subTest(verbose
=False),\
2329 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
2330 portfolio
.ReportStore
.set_verbose(False)
2331 portfolio
.ReportStore
.print_log("Coucou")
2332 portfolio
.ReportStore
.print_log(portfolio
.Amount("BTC", 1))
2333 self
.assertEqual(stdout_mock
.getvalue(), "")
2335 def test_to_json(self
):
2336 portfolio
.ReportStore
.logs
.append({"foo": "bar"}
)
2337 self
.assertEqual('[{"foo": "bar"}]', portfolio
.ReportStore
.to_json())
2338 portfolio
.ReportStore
.logs
.append({"date": portfolio.datetime(2018, 2, 24)}
)
2339 self
.assertEqual('[{"foo": "bar"}, {"date": "2018-02-24T00:00:00"}]', portfolio
.ReportStore
.to_json())
2340 portfolio
.ReportStore
.logs
.append({"amount": portfolio.Amount("BTC", 1)}
)
2341 with self
.assertRaises(TypeError):
2342 portfolio
.ReportStore
.to_json()
2344 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2345 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2346 def test_log_stage(self
, add_log
, print_log
):
2347 portfolio
.ReportStore
.log_stage("foo")
2348 print_log
.assert_has_calls([
2349 mock
.call("-----------"),
2350 mock
.call("[Stage] foo"),
2352 add_log
.assert_called_once_with({'type': 'stage', 'stage': 'foo'}
)
2354 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2355 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2356 @mock.patch("store.BalanceStore")
2357 def test_log_balances(self
, balance_store
, add_log
, print_log
):
2358 balance_store
.as_json
.return_value
= "json"
2359 balance_store
.all
= { "FOO": "bar", "BAR": "baz" }
2361 portfolio
.ReportStore
.log_balances("market", tag
="tag")
2362 print_log
.assert_has_calls([
2363 mock
.call("[Balance]"),
2367 add_log
.assert_called_once_with({
2373 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2374 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2375 def test_log_tickers(self
, add_log
, print_log
):
2376 market
= mock
.Mock()
2378 "BTC": portfolio
.Amount("BTC", 10),
2379 "ETH": portfolio
.Amount("BTC", D("0.3"))
2381 amounts
["ETH"].rate
= D("0.1")
2383 portfolio
.ReportStore
.log_tickers(market
, amounts
, "BTC", "default", "total")
2384 print_log
.assert_not_called()
2385 add_log
.assert_called_once_with({
2387 'compute_value': 'default',
2388 'balance_type': 'total',
2401 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2402 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2403 def test_log_dispatch(self
, add_log
, print_log
):
2404 amount
= portfolio
.Amount("BTC", "10.3")
2406 "BTC": portfolio
.Amount("BTC", 10),
2407 "ETH": portfolio
.Amount("BTC", D("0.3"))
2409 portfolio
.ReportStore
.log_dispatch(amount
, amounts
, "medium", "repartition")
2410 print_log
.assert_not_called()
2411 add_log
.assert_called_once_with({
2413 'liquidity': 'medium',
2414 'repartition_ratio': 'repartition',
2425 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2426 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2427 def test_log_trades(self
, add_log
, print_log
):
2428 trade_mock1
= mock
.Mock()
2429 trade_mock2
= mock
.Mock()
2430 trade_mock1
.as_json
.return_value
= { "trade": "1" }
2431 trade_mock2
.as_json
.return_value
= { "trade": "2" }
2433 matching_and_trades
= [
2434 (True, trade_mock1
),
2435 (False, trade_mock2
),
2437 portfolio
.ReportStore
.log_trades(matching_and_trades
, "only", "debug")
2439 print_log
.assert_not_called()
2440 add_log
.assert_called_with({
2445 {'trade': '1', 'skipped': False}
,
2446 {'trade': '2', 'skipped': True}
2450 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2451 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2452 @mock.patch.object(portfolio
.TradeStore
, "print_all_with_order")
2453 def test_log_orders(self
, print_all_with_order
, add_log
, print_log
):
2454 order_mock1
= mock
.Mock()
2455 order_mock2
= mock
.Mock()
2457 order_mock1
.as_json
.return_value
= "order1"
2458 order_mock2
.as_json
.return_value
= "order2"
2460 orders
= [order_mock1
, order_mock2
]
2462 portfolio
.ReportStore
.log_orders(orders
, tick
="tick",
2463 only
="only", compute_value
="compute_value")
2465 print_log
.assert_called_once_with("[Orders]")
2466 print_all_with_order
.assert_called_once_with(ind
="\t")
2468 add_log
.assert_called_with({
2471 'compute_value': 'compute_value',
2473 'orders': ['order1', 'order2']
2476 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2477 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2478 def test_log_order(self
, add_log
, print_log
):
2479 order_mock
= mock
.Mock()
2480 order_mock
.as_json
.return_value
= "order"
2481 new_order_mock
= mock
.Mock()
2482 new_order_mock
.as_json
.return_value
= "new_order"
2483 order_mock
.__repr
__ = mock
.Mock()
2484 order_mock
.__repr
__.return_value
= "Order Mock"
2485 new_order_mock
.__repr
__ = mock
.Mock()
2486 new_order_mock
.__repr
__.return_value
= "New order Mock"
2488 with self
.subTest(finished
=True):
2489 portfolio
.ReportStore
.log_order(order_mock
, 1, finished
=True)
2490 print_log
.assert_called_once_with("[Order] Finished Order Mock")
2491 add_log
.assert_called_once_with({
2496 'compute_value': None,
2500 add_log
.reset_mock()
2501 print_log
.reset_mock()
2503 with self
.subTest(update
="waiting"):
2504 portfolio
.ReportStore
.log_order(order_mock
, 1, update
="waiting")
2505 print_log
.assert_called_once_with("[Order] Order Mock, tick 1, waiting")
2506 add_log
.assert_called_once_with({
2509 'update': 'waiting',
2511 'compute_value': None,
2515 add_log
.reset_mock()
2516 print_log
.reset_mock()
2517 with self
.subTest(update
="adjusting"):
2518 portfolio
.ReportStore
.log_order(order_mock
, 3,
2519 update
="adjusting", new_order
=new_order_mock
,
2520 compute_value
="default")
2521 print_log
.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
2522 add_log
.assert_called_once_with({
2525 'update': 'adjusting',
2527 'compute_value': "default",
2528 'new_order': 'new_order'
2531 add_log
.reset_mock()
2532 print_log
.reset_mock()
2533 with self
.subTest(update
="market_fallback"):
2534 portfolio
.ReportStore
.log_order(order_mock
, 7,
2535 update
="market_fallback", new_order
=new_order_mock
)
2536 print_log
.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value")
2537 add_log
.assert_called_once_with({
2540 'update': 'market_fallback',
2542 'compute_value': None,
2543 'new_order': 'new_order'
2546 add_log
.reset_mock()
2547 print_log
.reset_mock()
2548 with self
.subTest(update
="market_adjusting"):
2549 portfolio
.ReportStore
.log_order(order_mock
, 17,
2550 update
="market_adjust", new_order
=new_order_mock
)
2551 print_log
.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock")
2552 add_log
.assert_called_once_with({
2555 'update': 'market_adjust',
2557 'compute_value': None,
2558 'new_order': 'new_order'
2561 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2562 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2563 def test_log_move_balances(self
, add_log
, print_log
):
2565 "BTC": portfolio
.Amount("BTC", 10),
2569 "BTC": portfolio
.Amount("BTC", 3),
2572 portfolio
.ReportStore
.log_move_balances(needed
, moving
, True)
2573 print_log
.assert_not_called()
2574 add_log
.assert_called_once_with({
2575 'type': 'move_balances',
2587 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2588 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2589 def test_log_http_request(self
, add_log
, print_log
):
2590 response
= mock
.Mock()
2591 response
.status_code
= 200
2592 response
.text
= "Hey"
2594 portfolio
.ReportStore
.log_http_request("method", "url", "body",
2595 "headers", response
)
2596 print_log
.assert_not_called()
2597 add_log
.assert_called_once_with({
2598 'type': 'http_request',
2602 'headers': 'headers',
2607 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2608 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2609 def test_log_error(self
, add_log
, print_log
):
2610 with self
.subTest(message
=None, exception
=None):
2611 portfolio
.ReportStore
.log_error("action")
2612 print_log
.assert_called_once_with("[Error] action")
2613 add_log
.assert_called_once_with({
2616 'exception_class': None,
2617 'exception_message': None,
2621 print_log
.reset_mock()
2622 add_log
.reset_mock()
2623 with self
.subTest(message
="Hey", exception
=None):
2624 portfolio
.ReportStore
.log_error("action", message
="Hey")
2625 print_log
.assert_has_calls([
2626 mock
.call("[Error] action"),
2629 add_log
.assert_called_once_with({
2632 'exception_class': None,
2633 'exception_message': None,
2637 print_log
.reset_mock()
2638 add_log
.reset_mock()
2639 with self
.subTest(message
=None, exception
=Exception("bouh")):
2640 portfolio
.ReportStore
.log_error("action", exception
=Exception("bouh"))
2641 print_log
.assert_has_calls([
2642 mock
.call("[Error] action"),
2643 mock
.call("\tException: bouh")
2645 add_log
.assert_called_once_with({
2648 'exception_class': "Exception",
2649 'exception_message': "bouh",
2653 print_log
.reset_mock()
2654 add_log
.reset_mock()
2655 with self
.subTest(message
="Hey", exception
=Exception("bouh")):
2656 portfolio
.ReportStore
.log_error("action", message
="Hey", exception
=Exception("bouh"))
2657 print_log
.assert_has_calls([
2658 mock
.call("[Error] action"),
2659 mock
.call("\tException: bouh"),
2662 add_log
.assert_called_once_with({
2665 'exception_class': "Exception",
2666 'exception_message': "bouh",
2670 @mock.patch.object(portfolio
.ReportStore
, "print_log")
2671 @mock.patch.object(portfolio
.ReportStore
, "add_log")
2672 def test_log_debug_action(self
, add_log
, print_log
):
2673 portfolio
.ReportStore
.log_debug_action("Hey")
2675 print_log
.assert_called_once_with("[Debug] Hey")
2676 add_log
.assert_called_once_with({
2677 'type': 'debug_action',
2681 @unittest.skipUnless("acceptance" in limits
, "Acceptance skipped")
2682 class AcceptanceTest(WebMockTestCase
):
2683 @unittest.expectedFailure
2684 def test_success_sell_only_necessary(self
):
2685 # FIXME: catch stdout
2686 portfolio
.ReportStore
.verbose_print
= False
2689 "exchange_free": D("1.0"),
2690 "exchange_used": D("0.0"),
2691 "exchange_total": D("1.0"),
2695 "exchange_free": D("4.0"),
2696 "exchange_used": D("0.0"),
2697 "exchange_total": D("4.0"),
2701 "exchange_free": D("1000.0"),
2702 "exchange_used": D("0.0"),
2703 "exchange_total": D("1000.0"),
2704 "total": D("1000.0"),
2708 "ETH": (D("0.25"), "long"),
2709 "ETC": (D("0.25"), "long"),
2710 "BTC": (D("0.4"), "long"),
2711 "BTD": (D("0.01"), "short"),
2712 "B2X": (D("0.04"), "long"),
2713 "USDT": (D("0.05"), "long"),
2716 def fetch_ticker(symbol
):
2717 if symbol
== "ETH/BTC":
2719 "symbol": "ETH/BTC",
2723 if symbol
== "ETC/BTC":
2725 "symbol": "ETC/BTC",
2729 if symbol
== "XVG/BTC":
2731 "symbol": "XVG/BTC",
2732 "bid": D("0.00003"),
2735 if symbol
== "BTD/BTC":
2737 "symbol": "BTD/BTC",
2741 if symbol
== "B2X/BTC":
2743 "symbol": "B2X/BTC",
2747 if symbol
== "USDT/BTC":
2748 raise helper
.ExchangeError
2749 if symbol
== "BTC/USDT":
2751 "symbol": "BTC/USDT",
2755 self
.fail("Shouldn't have been called with {}".format(symbol
))
2757 market
= mock
.Mock()
2758 market
.fetch_all_balances
.return_value
= fetch_balance
2759 market
.fetch_ticker
.side_effect
= fetch_ticker
2760 with mock
.patch
.object(portfolio
.Portfolio
, "repartition", return_value
=repartition
):
2762 helper
.prepare_trades(market
)
2764 balances
= portfolio
.BalanceStore
.all
2765 self
.assertEqual(portfolio
.Amount("ETH", 1), balances
["ETH"].total
)
2766 self
.assertEqual(portfolio
.Amount("ETC", 4), balances
["ETC"].total
)
2767 self
.assertEqual(portfolio
.Amount("XVG", 1000), balances
["XVG"].total
)
2770 trades
= portfolio
.TradeStore
.all
2771 self
.assertEqual(portfolio
.Amount("BTC", D("0.15")), trades
[0].value_from
)
2772 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[0].value_to
)
2773 self
.assertEqual("dispose", trades
[0].action
)
2775 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[1].value_from
)
2776 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[1].value_to
)
2777 self
.assertEqual("acquire", trades
[1].action
)
2779 self
.assertEqual(portfolio
.Amount("BTC", D("0.04")), trades
[2].value_from
)
2780 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[2].value_to
)
2781 self
.assertEqual("dispose", trades
[2].action
)
2783 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[3].value_from
)
2784 self
.assertEqual(portfolio
.Amount("BTC", D("-0.002")), trades
[3].value_to
)
2785 self
.assertEqual("acquire", trades
[3].action
)
2787 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[4].value_from
)
2788 self
.assertEqual(portfolio
.Amount("BTC", D("0.008")), trades
[4].value_to
)
2789 self
.assertEqual("acquire", trades
[4].action
)
2791 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[5].value_from
)
2792 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[5].value_to
)
2793 self
.assertEqual("acquire", trades
[5].action
)
2796 portfolio
.TradeStore
.prepare_orders(only
="dispose", compute_value
=lambda x
, y
: x
["bid"] * D("1.001"))
2798 all_orders
= portfolio
.TradeStore
.all_orders(state
="pending")
2799 self
.assertEqual(2, len(all_orders
))
2800 self
.assertEqual(2, 3*all_orders
[0].amount
.value
)
2801 self
.assertEqual(D("0.14014"), all_orders
[0].rate
)
2802 self
.assertEqual(1000, all_orders
[1].amount
.value
)
2803 self
.assertEqual(D("0.00003003"), all_orders
[1].rate
)
2806 def create_order(symbol
, type, action
, amount
, price
=None, account
="exchange"):
2807 self
.assertEqual("limit", type)
2808 if symbol
== "ETH/BTC":
2809 self
.assertEqual("sell", action
)
2810 self
.assertEqual(D('0.66666666'), amount
)
2811 self
.assertEqual(D("0.14014"), price
)
2812 elif symbol
== "XVG/BTC":
2813 self
.assertEqual("sell", action
)
2814 self
.assertEqual(1000, amount
)
2815 self
.assertEqual(D("0.00003003"), price
)
2817 self
.fail("I shouldn't have been called")
2822 market
.create_order
.side_effect
= create_order
2823 market
.order_precision
.return_value
= 8
2826 portfolio
.TradeStore
.run_orders()
2828 self
.assertEqual("open", all_orders
[0].status
)
2829 self
.assertEqual("open", all_orders
[1].status
)
2831 market
.fetch_order
.return_value
= { "status": "closed", "datetime": "2018-01-20 13:40:00" }
2832 market
.privatePostReturnOrderTrades
.return_value
= [
2834 "tradeID": 42, "type": "buy", "fee": "0.0015",
2835 "date": "2017-12-30 12:00:12", "rate": "0.1",
2836 "amount": "10", "total": "1"
2839 with mock
.patch
.object(portfolio
.time
, "sleep") as sleep
:
2841 helper
.follow_orders(verbose
=False)
2843 sleep
.assert_called_with(30)
2845 for order
in all_orders
:
2846 self
.assertEqual("closed", order
.status
)
2850 "exchange_free": D("1.0") / 3,
2851 "exchange_used": D("0.0"),
2852 "exchange_total": D("1.0") / 3,
2854 "total": D("1.0") / 3,
2857 "exchange_free": D("0.134"),
2858 "exchange_used": D("0.0"),
2859 "exchange_total": D("0.134"),
2861 "total": D("0.134"),
2864 "exchange_free": D("4.0"),
2865 "exchange_used": D("0.0"),
2866 "exchange_total": D("4.0"),
2871 "exchange_free": D("0.0"),
2872 "exchange_used": D("0.0"),
2873 "exchange_total": D("0.0"),
2878 market
.fetch_all_balances
.return_value
= fetch_balance
2880 with mock
.patch
.object(portfolio
.Portfolio
, "repartition", return_value
=repartition
):
2882 helper
.update_trades(market
, only
="acquire", compute_value
="average")
2884 balances
= portfolio
.BalanceStore
.all
2885 self
.assertEqual(portfolio
.Amount("ETH", 1 / D("3")), balances
["ETH"].total
)
2886 self
.assertEqual(portfolio
.Amount("ETC", 4), balances
["ETC"].total
)
2887 self
.assertEqual(portfolio
.Amount("BTC", D("0.134")), balances
["BTC"].total
)
2888 self
.assertEqual(portfolio
.Amount("XVG", 0), balances
["XVG"].total
)
2891 trades
= portfolio
.TradeStore
.all
2892 self
.assertEqual(portfolio
.Amount("BTC", D("0.15")), trades
[0].value_from
)
2893 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[0].value_to
)
2894 self
.assertEqual("dispose", trades
[0].action
)
2896 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[1].value_from
)
2897 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[1].value_to
)
2898 self
.assertEqual("acquire", trades
[1].action
)
2900 self
.assertNotIn("BTC", trades
)
2902 self
.assertEqual(portfolio
.Amount("BTC", D("0.04")), trades
[2].value_from
)
2903 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[2].value_to
)
2904 self
.assertEqual("dispose", trades
[2].action
)
2906 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[3].value_from
)
2907 self
.assertEqual(portfolio
.Amount("BTC", D("-0.002")), trades
[3].value_to
)
2908 self
.assertEqual("acquire", trades
[3].action
)
2910 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[4].value_from
)
2911 self
.assertEqual(portfolio
.Amount("BTC", D("0.008")), trades
[4].value_to
)
2912 self
.assertEqual("acquire", trades
[4].action
)
2914 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[5].value_from
)
2915 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[5].value_to
)
2916 self
.assertEqual("acquire", trades
[5].action
)
2919 portfolio
.TradeStore
.prepare_orders(only
="acquire", compute_value
=lambda x
, y
: x
["ask"])
2921 all_orders
= portfolio
.TradeStore
.all_orders(state
="pending")
2922 self
.assertEqual(4, len(all_orders
))
2923 self
.assertEqual(portfolio
.Amount("ETC", D("12.83333333")), round(all_orders
[0].amount
))
2924 self
.assertEqual(D("0.003"), all_orders
[0].rate
)
2925 self
.assertEqual("buy", all_orders
[0].action
)
2926 self
.assertEqual("long", all_orders
[0].trade_type
)
2928 self
.assertEqual(portfolio
.Amount("BTD", D("1.61666666")), round(all_orders
[1].amount
))
2929 self
.assertEqual(D("0.0012"), all_orders
[1].rate
)
2930 self
.assertEqual("sell", all_orders
[1].action
)
2931 self
.assertEqual("short", all_orders
[1].trade_type
)
2933 diff
= portfolio
.Amount("B2X", D("19.4")/3) - all_orders
[2].amount
2934 self
.assertAlmostEqual(0, diff
.value
)
2935 self
.assertEqual(D("0.0012"), all_orders
[2].rate
)
2936 self
.assertEqual("buy", all_orders
[2].action
)
2937 self
.assertEqual("long", all_orders
[2].trade_type
)
2939 self
.assertEqual(portfolio
.Amount("BTC", D("0.0097")), all_orders
[3].amount
)
2940 self
.assertEqual(D("16000"), all_orders
[3].rate
)
2941 self
.assertEqual("sell", all_orders
[3].action
)
2942 self
.assertEqual("long", all_orders
[3].trade_type
)
2946 # Move balances to margin
2950 # portfolio.TradeStore.run_orders()
2952 with mock
.patch
.object(portfolio
.time
, "sleep") as sleep
:
2954 helper
.follow_orders(verbose
=False)
2956 sleep
.assert_called_with(30)
2958 if __name__
== '__main__':