5 from decimal
import Decimal
as D
6 from unittest
import mock
9 from io
import StringIO
10 import portfolio
, market
, main
12 limits
= ["acceptance", "unit"]
13 for test_type
in limits
:
14 if "--no{}".format(test_type
) in sys
.argv
:
15 sys
.argv
.remove("--no{}".format(test_type
))
16 limits
.remove(test_type
)
17 if "--only{}".format(test_type
) in sys
.argv
:
18 sys
.argv
.remove("--only{}".format(test_type
))
22 class WebMockTestCase(unittest
.TestCase
):
26 super(WebMockTestCase
, self
).setUp()
27 self
.wm
= requests_mock
.Mocker()
31 self
.m
= mock
.Mock(name
="Market", spec
=market
.Market
)
35 mock
.patch
.multiple(portfolio
.Portfolio
, last_date
=None, data
=None, liquidities
={}),
36 mock
.patch
.multiple(portfolio
.Computation
,
37 computations
=portfolio
.Computation
.computations
),
39 for patcher
in self
.patchers
:
43 for patcher
in self
.patchers
:
46 super(WebMockTestCase
, self
).tearDown()
48 @unittest.skipUnless("unit" in limits
, "Unit skipped")
49 class poloniexETest(unittest
.TestCase
):
51 super(poloniexETest
, self
).setUp()
52 self
.wm
= requests_mock
.Mocker()
55 self
.s
= market
.ccxt
.poloniexE()
59 super(poloniexETest
, self
).tearDown()
61 def test_nanoseconds(self
):
62 with mock
.patch
.object(market
.ccxt
.time
, "time") as time
:
63 time
.return_value
= 123456.7890123456
64 self
.assertEqual(123456789012345, self
.s
.nanoseconds())
67 with mock
.patch
.object(market
.ccxt
.time
, "time") as time
:
68 time
.return_value
= 123456.7890123456
69 self
.assertEqual(123456789012345, self
.s
.nonce())
71 def test_order_precision(self
):
72 self
.assertEqual(8, self
.s
.order_precision("FOO"))
74 def test_transfer_balance(self
):
75 with self
.subTest(success
=True),\
76 mock
.patch
.object(self
.s
, "privatePostTransferBalance") as t
:
77 t
.return_value
= { "success": 1 }
78 result
= self
.s
.transfer_balance("FOO", 12, "exchange", "margin")
79 t
.assert_called_once_with({
82 "fromAccount": "exchange",
83 "toAccount": "margin",
86 self
.assertTrue(result
)
88 with self
.subTest(success
=False),\
89 mock
.patch
.object(self
.s
, "privatePostTransferBalance") as t
:
90 t
.return_value
= { "success": 0 }
91 self
.assertFalse(self
.s
.transfer_balance("FOO", 12, "exchange", "margin"))
93 def test_close_margin_position(self
):
94 with mock
.patch
.object(self
.s
, "privatePostCloseMarginPosition") as c
:
95 self
.s
.close_margin_position("FOO", "BAR")
96 c
.assert_called_with({"currencyPair": "BAR_FOO"}
)
98 def test_tradable_balances(self
):
99 with mock
.patch
.object(self
.s
, "privatePostReturnTradableBalances") as r
:
101 "FOO": { "exchange": "12.1234", "margin": "0.0123" }
,
102 "BAR": { "exchange": "1", "margin": "0" }
,
104 balances
= self
.s
.tradable_balances()
105 self
.assertEqual(["FOO", "BAR"], list(balances
.keys()))
106 self
.assertEqual(["exchange", "margin"], list(balances
["FOO"].keys()))
107 self
.assertEqual(D("12.1234"), balances
["FOO"]["exchange"])
108 self
.assertEqual(["exchange", "margin"], list(balances
["BAR"].keys()))
110 def test_margin_summary(self
):
111 with mock
.patch
.object(self
.s
, "privatePostReturnMarginAccountSummary") as r
:
113 "currentMargin": "1.49680968",
114 "lendingFees": "0.0000001",
116 "totalBorrowedValue": "0.00673602",
117 "totalValue": "0.01000000",
118 "netValue": "0.01008254",
121 'current_margin': D('1.49680968'),
122 'gains': D('0.00008254'),
123 'lending_fees': D('0.0000001'),
124 'total': D('0.01000000'),
125 'total_borrowed': D('0.00673602')
127 self
.assertEqual(expected
, self
.s
.margin_summary())
129 def test_create_order(self
):
130 with mock
.patch
.object(self
.s
, "create_exchange_order") as exchange
,\
131 mock
.patch
.object(self
.s
, "create_margin_order") as margin
:
132 with self
.subTest(account
="unspecified"):
133 self
.s
.create_order("symbol", "type", "side", "amount", price
="price", lending_rate
="lending_rate", params
="params")
134 exchange
.assert_called_once_with("symbol", "type", "side", "amount", price
="price", params
="params")
135 margin
.assert_not_called()
136 exchange
.reset_mock()
139 with self
.subTest(account
="exchange"):
140 self
.s
.create_order("symbol", "type", "side", "amount", account
="exchange", price
="price", lending_rate
="lending_rate", params
="params")
141 exchange
.assert_called_once_with("symbol", "type", "side", "amount", price
="price", params
="params")
142 margin
.assert_not_called()
143 exchange
.reset_mock()
146 with self
.subTest(account
="margin"):
147 self
.s
.create_order("symbol", "type", "side", "amount", account
="margin", price
="price", lending_rate
="lending_rate", params
="params")
148 margin
.assert_called_once_with("symbol", "type", "side", "amount", lending_rate
="lending_rate", price
="price", params
="params")
149 exchange
.assert_not_called()
150 exchange
.reset_mock()
153 with self
.subTest(account
="unknown"), self
.assertRaises(NotImplementedError):
154 self
.s
.create_order("symbol", "type", "side", "amount", account
="unknown")
156 def test_parse_ticker(self
):
160 "highestBid": "10.5",
163 "percentChange": "0.1",
170 with mock
.patch
.object(self
.s
, "milliseconds") as ms
:
171 ms
.return_value
= 1520292715123
172 result
= self
.s
.parse_ticker(ticker
, market
)
176 "timestamp": 1520292715123,
177 "datetime": "2018-03-05T23:31:55.123Z",
190 "baseVolume": D("10"),
191 "quoteVolume": D("20"),
194 self
.assertEqual(expected
, result
)
196 def test_fetch_margin_balance(self
):
197 with mock
.patch
.object(self
.s
, "privatePostGetMarginPosition") as get_margin_position
:
198 get_margin_position
.return_value
= {
201 "basePrice": "0.06818560",
202 "lendingFees": "0.00000001",
203 "liquidationPrice": "0.15107132",
205 "total": "0.00681856",
211 "lendingFees": "0.00000001",
212 "liquidationPrice": "0.6",
221 "liquidationPrice": "-1",
227 balances
= self
.s
.fetch_margin_balance()
228 self
.assertEqual(2, len(balances
))
232 "borrowedPrice": D("0.06818560"),
233 "lendingFees": D("1E-8"),
234 "pl": D("-0.00000371"),
235 "liquidationPrice": D("0.15107132"),
237 "total": D("0.00681856"),
238 "baseCurrency": "BTC"
242 "borrowedPrice": D("0.1"),
243 "lendingFees": D("1E-8"),
244 "pl": D("0.00000371"),
245 "liquidationPrice": D("0.6"),
248 "baseCurrency": "BTC"
251 self
.assertEqual(expected
, balances
)
254 self
.assertEqual(D("1.1"), self
.s
.sum(D("1"), D("0.1")))
256 def test_fetch_balance(self
):
257 with mock
.patch
.object(self
.s
, "load_markets") as load_markets
,\
258 mock
.patch
.object(self
.s
, "privatePostReturnCompleteBalances") as balances
,\
259 mock
.patch
.object(self
.s
, "common_currency_code") as ccc
:
260 ccc
.side_effect
= ["ETH", "BTC", "DASH"]
261 balances
.return_value
= {
278 "ETH": {"available": "10", "onOrders": "1"}
,
279 "BTC": {"available": "1", "onOrders": "0"}
,
280 "DASH": {"available": "0", "onOrders": "3"}
282 "ETH": {"free": D("10"), "used": D("1"), "total": D("11")}
,
283 "BTC": {"free": D("1"), "used": D("0"), "total": D("1")}
,
284 "DASH": {"free": D("0"), "used": D("3"), "total": D("3")}
,
285 "free": {"ETH": D("10"), "BTC": D("1"), "DASH": D("0")}
,
286 "used": {"ETH": D("1"), "BTC": D("0"), "DASH": D("3")}
,
287 "total": {"ETH": D("11"), "BTC": D("1"), "DASH": D("3")}
289 result
= self
.s
.fetch_balance()
290 load_markets
.assert_called_once()
291 self
.assertEqual(expected
, result
)
293 def test_fetch_balance_per_type(self
):
294 with mock
.patch
.object(self
.s
, "privatePostReturnAvailableAccountBalances") as balances
:
295 balances
.return_value
= {
297 "BLK": "159.83673869",
299 "USDT": "0.00002625",
309 "BLK": "159.83673869",
311 "USDT": "0.00002625",
319 "BLK": D("159.83673869"),
320 "BTC": D("0.00005959"),
321 "USDT": D("0.00002625"),
322 "XMR": D("0.18719303")
324 "margin": {"BTC": D("0.03019227")}
,
325 "BLK": {"exchange": D("159.83673869")}
,
326 "BTC": {"exchange": D("0.00005959"), "margin": D("0.03019227")}
,
327 "USDT": {"exchange": D("0.00002625")}
,
328 "XMR": {"exchange": D("0.18719303")}
330 result
= self
.s
.fetch_balance_per_type()
331 self
.assertEqual(expected
, result
)
333 def test_fetch_all_balances(self
):
335 with mock
.patch
.object(self
.s
, "load_markets") as load_markets
,\
336 mock
.patch
.object(self
.s
, "privatePostGetMarginPosition") as margin_balance
,\
337 mock
.patch
.object(self
.s
, "privatePostReturnCompleteBalances") as balance
,\
338 mock
.patch
.object(self
.s
, "privatePostReturnAvailableAccountBalances") as balance_per_type
:
340 with open("test_samples/poloniexETest.test_fetch_all_balances.1.json") as f
:
341 balance
.return_value
= json
.load(f
)
342 with open("test_samples/poloniexETest.test_fetch_all_balances.2.json") as f
:
343 margin_balance
.return_value
= json
.load(f
)
344 with open("test_samples/poloniexETest.test_fetch_all_balances.3.json") as f
:
345 balance_per_type
.return_value
= json
.load(f
)
347 result
= self
.s
.fetch_all_balances()
349 "total": D("-12779.79821852"),
350 "exchange_used": D("0E-8"),
351 "exchange_total": D("0E-8"),
352 "exchange_free": D("0E-8"),
353 "margin_available": 0,
354 "margin_in_position": 0,
355 "margin_borrowed": D("12779.79821852"),
356 "margin_total": D("-12779.79821852"),
357 "margin_pending_gain": 0,
358 "margin_lending_fees": D("-9E-8"),
359 "margin_pending_base_gain": D("0.00024059"),
360 "margin_position_type": "short",
361 "margin_liquidation_price": D("0.00000246"),
362 "margin_borrowed_base_price": D("0.00599149"),
363 "margin_borrowed_base_currency": "BTC"
365 expected_btc
= {"total": D("0.05432165"),
366 "exchange_used": D("0E-8"),
367 "exchange_total": D("0.00005959"),
368 "exchange_free": D("0.00005959"),
369 "margin_available": D("0.03019227"),
370 "margin_in_position": D("0.02406979"),
371 "margin_borrowed": 0,
372 "margin_total": D("0.05426206"),
373 "margin_pending_gain": D("0.00093955"),
374 "margin_lending_fees": 0,
375 "margin_pending_base_gain": 0,
376 "margin_position_type": None,
377 "margin_liquidation_price": 0,
378 "margin_borrowed_base_price": 0,
379 "margin_borrowed_base_currency": None
381 expected_xmr
= {"total": D("0.18719303"),
382 "exchange_used": D("0E-8"),
383 "exchange_total": D("0.18719303"),
384 "exchange_free": D("0.18719303"),
385 "margin_available": 0,
386 "margin_in_position": 0,
387 "margin_borrowed": 0,
389 "margin_pending_gain": 0,
390 "margin_lending_fees": 0,
391 "margin_pending_base_gain": 0,
392 "margin_position_type": None,
393 "margin_liquidation_price": 0,
394 "margin_borrowed_base_price": 0,
395 "margin_borrowed_base_currency": None
397 self
.assertEqual(expected_xmr
, result
["XMR"])
398 self
.assertEqual(expected_doge
, result
["DOGE"])
399 self
.assertEqual(expected_btc
, result
["BTC"])
401 def test_create_margin_order(self
):
402 with self
.assertRaises(market
.ExchangeError
):
403 self
.s
.create_margin_order("FOO", "market", "buy", "10")
405 with mock
.patch
.object(self
.s
, "load_markets") as load_markets
,\
406 mock
.patch
.object(self
.s
, "privatePostMarginBuy") as margin_buy
,\
407 mock
.patch
.object(self
.s
, "privatePostMarginSell") as margin_sell
,\
408 mock
.patch
.object(self
.s
, "market") as market_mock
,\
409 mock
.patch
.object(self
.s
, "price_to_precision") as ptp
,\
410 mock
.patch
.object(self
.s
, "amount_to_precision") as atp
:
412 margin_buy
.return_value
= {
415 margin_sell
.return_value
= {
418 market_mock
.return_value
= { "id": "BTC_ETC", "symbol": "BTC_ETC" }
419 ptp
.return_value
= D("0.1")
420 atp
.return_value
= D("12")
422 order
= self
.s
.create_margin_order("BTC_ETC", "margin", "buy", "12", price
="0.1")
423 self
.assertEqual(123, order
["id"])
424 margin_buy
.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")}
)
425 margin_sell
.assert_not_called()
426 margin_buy
.reset_mock()
427 margin_sell
.reset_mock()
429 order
= self
.s
.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate
="0.01", price
="0.1")
430 self
.assertEqual(456, order
["id"])
431 margin_sell
.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"}
)
432 margin_buy
.assert_not_called()
434 def test_create_exchange_order(self
):
435 with mock
.patch
.object(market
.ccxt
.poloniex
, "create_order") as create_order
:
436 self
.s
.create_order("symbol", "type", "side", "amount", price
="price", params
="params")
438 create_order
.assert_called_once_with("symbol", "type", "side", "amount", price
="price", params
="params")
440 @unittest.skipUnless("unit" in limits
, "Unit skipped")
441 class PortfolioTest(WebMockTestCase
):
443 if self
.json_response
is not None:
444 portfolio
.Portfolio
.data
= self
.json_response
447 super(PortfolioTest
, self
).setUp()
449 with open("test_samples/test_portfolio.json") as example
:
450 self
.json_response
= example
.read()
452 self
.wm
.get(portfolio
.Portfolio
.URL
, text
=self
.json_response
)
454 def test_get_cryptoportfolio(self
):
455 self
.wm
.get(portfolio
.Portfolio
.URL
, [
456 {"text":'{ "foo": "bar" }
', "status_code": 200},
457 {"text": "System Error", "status_code": 500},
458 {"exc": requests.exceptions.ConnectTimeout},
460 portfolio.Portfolio.get_cryptoportfolio(self.m)
461 self.assertIn("foo", portfolio.Portfolio.data)
462 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
463 self.assertTrue(self.wm.called)
464 self.assertEqual(1, self.wm.call_count)
465 self.m.report.log_error.assert_not_called()
466 self.m.report.log_http_request.assert_called_once()
467 self.m.report.log_http_request.reset_mock()
469 portfolio.Portfolio.get_cryptoportfolio(self.m)
470 self.assertIsNone(portfolio.Portfolio.data)
471 self.assertEqual(2, self.wm.call_count)
472 self.m.report.log_error.assert_not_called()
473 self.m.report.log_http_request.assert_called_once()
474 self.m.report.log_http_request.reset_mock()
477 portfolio.Portfolio.data = "Foo"
478 portfolio.Portfolio.get_cryptoportfolio(self.m)
479 self.assertEqual("Foo", portfolio.Portfolio.data)
480 self.assertEqual(3, self.wm.call_count)
481 self.m.report.log_error.assert_called_once_with("get_cryptoportfolio",
483 self.m.report.log_http_request.assert_not_called()
485 def test_parse_cryptoportfolio(self):
486 portfolio.Portfolio.parse_cryptoportfolio(self.m)
488 self.assertListEqual(
490 list(portfolio.Portfolio.liquidities.keys()))
492 liquidities = portfolio.Portfolio.liquidities
493 self.assertEqual(10, len(liquidities["medium"].keys()))
494 self.assertEqual(10, len(liquidities["high"].keys()))
497 'BTC
': (D("0.2857"), "long"),
498 'DGB
': (D("0.1015"), "long"),
499 'DOGE
': (D("0.1805"), "long"),
500 'SC
': (D("0.0623"), "long"),
501 'ZEC
': (D("0.3701"), "long"),
503 date = portfolio.datetime(2018, 1, 8)
504 self.assertDictEqual(expected, liquidities["high"][date])
507 'BTC
': (D("1.1102e-16"), "long"),
508 'ETC
': (D("0.1"), "long"),
509 'FCT
': (D("0.1"), "long"),
510 'GAS
': (D("0.1"), "long"),
511 'NAV
': (D("0.1"), "long"),
512 'OMG
': (D("0.1"), "long"),
513 'OMNI
': (D("0.1"), "long"),
514 'PPC
': (D("0.1"), "long"),
515 'RIC
': (D("0.1"), "long"),
516 'VIA
': (D("0.1"), "long"),
517 'XCP
': (D("0.1"), "long"),
519 self.assertDictEqual(expected, liquidities["medium"][date])
520 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)
522 self.m.report.log_http_request.assert_called_once_with("GET",
523 portfolio.Portfolio.URL, None, mock.ANY, mock.ANY)
524 self.m.report.log_http_request.reset_mock()
526 # It doesn't refetch the data when available
527 portfolio
.Portfolio
.parse_cryptoportfolio(self
.m
)
528 self
.m
.report
.log_http_request
.assert_not_called()
530 self
.assertEqual(1, self
.wm
.call_count
)
532 portfolio
.Portfolio
.parse_cryptoportfolio(self
.m
, refetch
=True)
533 self
.assertEqual(2, self
.wm
.call_count
)
534 self
.m
.report
.log_http_request
.assert_called_once()
536 def test_repartition(self
):
538 'BTC': (D("1.1102e-16"), "long"),
539 'USDT': (D("0.1"), "long"),
540 'ETC': (D("0.1"), "long"),
541 'FCT': (D("0.1"), "long"),
542 'OMG': (D("0.1"), "long"),
543 'STEEM': (D("0.1"), "long"),
544 'STRAT': (D("0.1"), "long"),
545 'XEM': (D("0.1"), "long"),
546 'XMR': (D("0.1"), "long"),
547 'XVC': (D("0.1"), "long"),
548 'ZRX': (D("0.1"), "long"),
551 'USDT': (D("0.1226"), "long"),
552 'BTC': (D("0.1429"), "long"),
553 'ETC': (D("0.1127"), "long"),
554 'ETH': (D("0.1569"), "long"),
555 'FCT': (D("0.3341"), "long"),
556 'GAS': (D("0.1308"), "long"),
559 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition(self
.m
))
560 self
.assertEqual(expected_medium
, portfolio
.Portfolio
.repartition(self
.m
, liquidity
="medium"))
561 self
.assertEqual(expected_high
, portfolio
.Portfolio
.repartition(self
.m
, liquidity
="high"))
563 self
.assertEqual(1, self
.wm
.call_count
)
565 portfolio
.Portfolio
.repartition(self
.m
)
566 self
.assertEqual(1, self
.wm
.call_count
)
568 portfolio
.Portfolio
.repartition(self
.m
, refetch
=True)
569 self
.assertEqual(2, self
.wm
.call_count
)
570 self
.m
.report
.log_http_request
.assert_called()
571 self
.assertEqual(2, self
.m
.report
.log_http_request
.call_count
)
573 @mock.patch.object(portfolio
.time
, "sleep")
574 @mock.patch.object(portfolio
.Portfolio
, "repartition")
575 def test_wait_for_recent(self
, repartition
, sleep
):
577 def _repartition(market
, refetch
):
578 self
.assertEqual(self
.m
, market
)
579 self
.assertTrue(refetch
)
581 portfolio
.Portfolio
.last_date
= portfolio
.datetime
.now()\
582 - portfolio
.timedelta(10)\
583 + portfolio
.timedelta(self
.call_count
)
584 repartition
.side_effect
= _repartition
586 portfolio
.Portfolio
.wait_for_recent(self
.m
)
587 sleep
.assert_called_with(30)
588 self
.assertEqual(6, sleep
.call_count
)
589 self
.assertEqual(7, repartition
.call_count
)
590 self
.m
.report
.print_log
.assert_called_with("Attempt to fetch up-to-date cryptoportfolio")
593 repartition
.reset_mock()
594 portfolio
.Portfolio
.last_date
= None
596 portfolio
.Portfolio
.wait_for_recent(self
.m
, delta
=15)
597 sleep
.assert_not_called()
598 self
.assertEqual(1, repartition
.call_count
)
601 repartition
.reset_mock()
602 portfolio
.Portfolio
.last_date
= None
604 portfolio
.Portfolio
.wait_for_recent(self
.m
, delta
=1)
605 sleep
.assert_called_with(30)
606 self
.assertEqual(9, sleep
.call_count
)
607 self
.assertEqual(10, repartition
.call_count
)
609 @unittest.skipUnless("unit" in limits
, "Unit skipped")
610 class AmountTest(WebMockTestCase
):
611 def test_values(self
):
612 amount
= portfolio
.Amount("BTC", "0.65")
613 self
.assertEqual(D("0.65"), amount
.value
)
614 self
.assertEqual("BTC", amount
.currency
)
616 def test_in_currency(self
):
617 amount
= portfolio
.Amount("ETC", 10)
619 self
.assertEqual(amount
, amount
.in_currency("ETC", self
.m
))
621 with self
.subTest(desc
="no ticker for currency"):
622 self
.m
.get_ticker
.return_value
= None
624 self
.assertRaises(Exception, amount
.in_currency
, "ETH", self
.m
)
626 with self
.subTest(desc
="nominal case"):
627 self
.m
.get_ticker
.return_value
= {
633 converted_amount
= amount
.in_currency("ETH", self
.m
)
635 self
.assertEqual(D("3.0"), converted_amount
.value
)
636 self
.assertEqual("ETH", converted_amount
.currency
)
637 self
.assertEqual(amount
, converted_amount
.linked_to
)
638 self
.assertEqual("bar", converted_amount
.ticker
["foo"])
640 converted_amount
= amount
.in_currency("ETH", self
.m
, action
="bid", compute_value
="default")
641 self
.assertEqual(D("2"), converted_amount
.value
)
643 converted_amount
= amount
.in_currency("ETH", self
.m
, compute_value
="ask")
644 self
.assertEqual(D("4"), converted_amount
.value
)
646 converted_amount
= amount
.in_currency("ETH", self
.m
, rate
=D("0.02"))
647 self
.assertEqual(D("0.2"), converted_amount
.value
)
649 def test__round(self
):
650 amount
= portfolio
.Amount("BAR", portfolio
.D("1.23456789876"))
651 self
.assertEqual(D("1.23456789"), round(amount
).value
)
652 self
.assertEqual(D("1.23"), round(amount
, 2).value
)
655 amount
= portfolio
.Amount("SC", -120)
656 self
.assertEqual(120, abs(amount
).value
)
657 self
.assertEqual("SC", abs(amount
).currency
)
659 amount
= portfolio
.Amount("SC", 10)
660 self
.assertEqual(10, abs(amount
).value
)
661 self
.assertEqual("SC", abs(amount
).currency
)
664 amount1
= portfolio
.Amount("XVG", "12.9")
665 amount2
= portfolio
.Amount("XVG", "13.1")
667 self
.assertEqual(26, (amount1
+ amount2
).value
)
668 self
.assertEqual("XVG", (amount1
+ amount2
).currency
)
670 amount3
= portfolio
.Amount("ETH", "1.6")
671 with self
.assertRaises(Exception):
674 amount4
= portfolio
.Amount("ETH", 0.0)
675 self
.assertEqual(amount1
, amount1
+ amount4
)
677 self
.assertEqual(amount1
, amount1
+ 0)
679 def test__radd(self
):
680 amount
= portfolio
.Amount("XVG", "12.9")
682 self
.assertEqual(amount
, 0 + amount
)
683 with self
.assertRaises(Exception):
687 amount1
= portfolio
.Amount("XVG", "13.3")
688 amount2
= portfolio
.Amount("XVG", "13.1")
690 self
.assertEqual(D("0.2"), (amount1
- amount2
).value
)
691 self
.assertEqual("XVG", (amount1
- amount2
).currency
)
693 amount3
= portfolio
.Amount("ETH", "1.6")
694 with self
.assertRaises(Exception):
697 amount4
= portfolio
.Amount("ETH", 0.0)
698 self
.assertEqual(amount1
, amount1
- amount4
)
700 def test__rsub(self
):
701 amount
= portfolio
.Amount("ETH", "1.6")
702 with self
.assertRaises(Exception):
705 self
.assertEqual(portfolio
.Amount("ETH", "-1.6"), 0-amount
)
708 amount
= portfolio
.Amount("XEM", 11)
710 self
.assertEqual(D("38.5"), (amount
* D("3.5")).value
)
711 self
.assertEqual(D("33"), (amount
* 3).value
)
713 with self
.assertRaises(Exception):
716 def test__rmul(self
):
717 amount
= portfolio
.Amount("XEM", 11)
719 self
.assertEqual(D("38.5"), (D("3.5") * amount
).value
)
720 self
.assertEqual(D("33"), (3 * amount
).value
)
722 def test__floordiv(self
):
723 amount
= portfolio
.Amount("XEM", 11)
725 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
726 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
728 with self
.assertRaises(Exception):
731 def test__truediv(self
):
732 amount
= portfolio
.Amount("XEM", 11)
734 self
.assertEqual(D("5.5"), (amount
/ 2).value
)
735 self
.assertEqual(D("4.4"), (amount
/ D("2.5")).value
)
738 amount1
= portfolio
.Amount("BTD", 11.3)
739 amount2
= portfolio
.Amount("BTD", 13.1)
741 self
.assertTrue(amount1
< amount2
)
742 self
.assertFalse(amount2
< amount1
)
743 self
.assertFalse(amount1
< amount1
)
745 amount3
= portfolio
.Amount("BTC", 1.6)
746 with self
.assertRaises(Exception):
750 amount1
= portfolio
.Amount("BTD", 11.3)
751 amount2
= portfolio
.Amount("BTD", 13.1)
753 self
.assertTrue(amount1
<= amount2
)
754 self
.assertFalse(amount2
<= amount1
)
755 self
.assertTrue(amount1
<= amount1
)
757 amount3
= portfolio
.Amount("BTC", 1.6)
758 with self
.assertRaises(Exception):
762 amount1
= portfolio
.Amount("BTD", 11.3)
763 amount2
= portfolio
.Amount("BTD", 13.1)
765 self
.assertTrue(amount2
> amount1
)
766 self
.assertFalse(amount1
> amount2
)
767 self
.assertFalse(amount1
> amount1
)
769 amount3
= portfolio
.Amount("BTC", 1.6)
770 with self
.assertRaises(Exception):
774 amount1
= portfolio
.Amount("BTD", 11.3)
775 amount2
= portfolio
.Amount("BTD", 13.1)
777 self
.assertTrue(amount2
>= amount1
)
778 self
.assertFalse(amount1
>= amount2
)
779 self
.assertTrue(amount1
>= amount1
)
781 amount3
= portfolio
.Amount("BTC", 1.6)
782 with self
.assertRaises(Exception):
786 amount1
= portfolio
.Amount("BTD", 11.3)
787 amount2
= portfolio
.Amount("BTD", 13.1)
788 amount3
= portfolio
.Amount("BTD", 11.3)
790 self
.assertFalse(amount1
== amount2
)
791 self
.assertFalse(amount2
== amount1
)
792 self
.assertTrue(amount1
== amount3
)
793 self
.assertFalse(amount2
== 0)
795 amount4
= portfolio
.Amount("BTC", 1.6)
796 with self
.assertRaises(Exception):
799 amount5
= portfolio
.Amount("BTD", 0)
800 self
.assertTrue(amount5
== 0)
803 amount1
= portfolio
.Amount("BTD", 11.3)
804 amount2
= portfolio
.Amount("BTD", 13.1)
805 amount3
= portfolio
.Amount("BTD", 11.3)
807 self
.assertTrue(amount1
!= amount2
)
808 self
.assertTrue(amount2
!= amount1
)
809 self
.assertFalse(amount1
!= amount3
)
810 self
.assertTrue(amount2
!= 0)
812 amount4
= portfolio
.Amount("BTC", 1.6)
813 with self
.assertRaises(Exception):
816 amount5
= portfolio
.Amount("BTD", 0)
817 self
.assertFalse(amount5
!= 0)
820 amount1
= portfolio
.Amount("BTD", "11.3")
822 self
.assertEqual(portfolio
.D("-11.3"), (-amount1
).value
)
825 amount1
= portfolio
.Amount("BTX", 32)
826 self
.assertEqual("32.00000000 BTX", str(amount1
))
828 amount2
= portfolio
.Amount("USDT", 12000)
829 amount1
.linked_to
= amount2
830 self
.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1
))
832 def test__repr(self
):
833 amount1
= portfolio
.Amount("BTX", 32)
834 self
.assertEqual("Amount(32.00000000 BTX)", repr(amount1
))
836 amount2
= portfolio
.Amount("USDT", 12000)
837 amount1
.linked_to
= amount2
838 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1
))
840 amount3
= portfolio
.Amount("BTC", 0.1)
841 amount2
.linked_to
= amount3
842 self
.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1
))
844 def test_as_json(self
):
845 amount
= portfolio
.Amount("BTX", 32)
846 self
.assertEqual({"currency": "BTX", "value": D("32")}
, amount
.as_json())
848 amount
= portfolio
.Amount("BTX", "1E-10")
849 self
.assertEqual({"currency": "BTX", "value": D("0")}
, amount
.as_json())
851 amount
= portfolio
.Amount("BTX", "1E-5")
852 self
.assertEqual({"currency": "BTX", "value": D("0.00001")}
, amount
.as_json())
853 self
.assertEqual("0.00001", str(amount
.as_json()["value"]))
855 @unittest.skipUnless("unit" in limits
, "Unit skipped")
856 class BalanceTest(WebMockTestCase
):
857 def test_values(self
):
858 balance
= portfolio
.Balance("BTC", {
859 "exchange_total": "0.65",
860 "exchange_free": "0.35",
861 "exchange_used": "0.30",
862 "margin_total": "-10",
863 "margin_borrowed": "10",
864 "margin_available": "0",
865 "margin_in_position": "0",
866 "margin_position_type": "short",
867 "margin_borrowed_base_currency": "USDT",
868 "margin_liquidation_price": "1.20",
869 "margin_pending_gain": "10",
870 "margin_lending_fees": "0.4",
871 "margin_borrowed_base_price": "0.15",
873 self
.assertEqual(portfolio
.D("0.65"), balance
.exchange_total
.value
)
874 self
.assertEqual(portfolio
.D("0.35"), balance
.exchange_free
.value
)
875 self
.assertEqual(portfolio
.D("0.30"), balance
.exchange_used
.value
)
876 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
877 self
.assertEqual("BTC", balance
.exchange_free
.currency
)
878 self
.assertEqual("BTC", balance
.exchange_total
.currency
)
880 self
.assertEqual(portfolio
.D("-10"), balance
.margin_total
.value
)
881 self
.assertEqual(portfolio
.D("10"), balance
.margin_borrowed
.value
)
882 self
.assertEqual(portfolio
.D("0"), balance
.margin_available
.value
)
883 self
.assertEqual("BTC", balance
.margin_total
.currency
)
884 self
.assertEqual("BTC", balance
.margin_borrowed
.currency
)
885 self
.assertEqual("BTC", balance
.margin_available
.currency
)
887 self
.assertEqual("BTC", balance
.currency
)
889 self
.assertEqual(portfolio
.D("0.4"), balance
.margin_lending_fees
.value
)
890 self
.assertEqual("USDT", balance
.margin_lending_fees
.currency
)
892 def test__repr(self
):
893 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
894 repr(portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)))
895 balance
= portfolio
.Balance("BTX", { "exchange_total": 3,
896 "exchange_used": 1, "exchange_free": 2 })
897 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
899 balance
= portfolio
.Balance("BTX", { "exchange_total": 1, "exchange_used": 1}
)
900 self
.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance
))
902 balance
= portfolio
.Balance("BTX", { "margin_total": 3,
903 "margin_in_position": 1, "margin_available": 2 })
904 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance
))
906 balance
= portfolio
.Balance("BTX", { "margin_total": 2, "margin_available": 2 }
)
907 self
.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance
))
909 balance
= portfolio
.Balance("BTX", { "margin_total": -3,
910 "margin_borrowed_base_price": D("0.1"),
911 "margin_borrowed_base_currency": "BTC",
912 "margin_lending_fees": D("0.002") })
913 self
.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance
))
915 balance
= portfolio
.Balance("BTX", { "margin_total": 1,
916 "margin_in_position": 1, "exchange_free": 2, "exchange_total": 2})
917 self
.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance
))
919 def test_as_json(self
):
920 balance
= portfolio
.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }
)
921 as_json
= balance
.as_json()
922 self
.assertEqual(set(portfolio
.Balance
.base_keys
), set(as_json
.keys()))
923 self
.assertEqual(D(0), as_json
["total"])
924 self
.assertEqual(D(2), as_json
["exchange_total"])
925 self
.assertEqual(D(2), as_json
["exchange_free"])
926 self
.assertEqual(D(0), as_json
["exchange_used"])
927 self
.assertEqual(D(0), as_json
["margin_total"])
928 self
.assertEqual(D(0), as_json
["margin_available"])
929 self
.assertEqual(D(0), as_json
["margin_borrowed"])
931 @unittest.skipUnless("unit" in limits
, "Unit skipped")
932 class MarketTest(WebMockTestCase
):
934 super(MarketTest
, self
).setUp()
936 self
.ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
938 def test_values(self
):
939 m
= market
.Market(self
.ccxt
)
941 self
.assertEqual(self
.ccxt
, m
.ccxt
)
942 self
.assertFalse(m
.debug
)
943 self
.assertIsInstance(m
.report
, market
.ReportStore
)
944 self
.assertIsInstance(m
.trades
, market
.TradeStore
)
945 self
.assertIsInstance(m
.balances
, market
.BalanceStore
)
946 self
.assertEqual(m
, m
.report
.market
)
947 self
.assertEqual(m
, m
.trades
.market
)
948 self
.assertEqual(m
, m
.balances
.market
)
949 self
.assertEqual(m
, m
.ccxt
._market
)
951 m
= market
.Market(self
.ccxt
, debug
=True)
952 self
.assertTrue(m
.debug
)
954 m
= market
.Market(self
.ccxt
, debug
=False)
955 self
.assertFalse(m
.debug
)
957 @mock.patch("market.ccxt")
958 def test_from_config(self
, ccxt
):
959 with mock
.patch("market.ReportStore"):
960 ccxt
.poloniexE
.return_value
= self
.ccxt
961 self
.ccxt
.session
.request
.return_value
= "response"
963 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
)
965 self
.assertEqual(self
.ccxt
, m
.ccxt
)
967 self
.ccxt
.session
.request("GET", "URL", data
="data",
969 m
.report
.log_http_request
.assert_called_with('GET', 'URL', 'data',
970 'headers', 'response')
972 m
= market
.Market
.from_config({"key": "key", "secred": "secret"}
, debug
=True)
973 self
.assertEqual(True, m
.debug
)
975 def test_get_tickers(self
):
976 self
.ccxt
.fetch_tickers
.side_effect
= [
981 m
= market
.Market(self
.ccxt
)
982 self
.assertEqual("tickers", m
.get_tickers())
983 self
.assertEqual("tickers", m
.get_tickers())
984 self
.ccxt
.fetch_tickers
.assert_called_once()
986 self
.assertIsNone(m
.get_tickers(refresh
=self
.time
.time()))
988 def test_get_ticker(self
):
989 with self
.subTest(get_tickers
=True):
990 self
.ccxt
.fetch_tickers
.return_value
= {
991 "ETH/ETC": { "bid": 1, "ask": 3 }
,
992 "XVG/ETH": { "bid": 10, "ask": 40 }
,
994 m
= market
.Market(self
.ccxt
)
996 ticker
= m
.get_ticker("ETH", "ETC")
997 self
.assertEqual(1, ticker
["bid"])
998 self
.assertEqual(3, ticker
["ask"])
999 self
.assertEqual(2, ticker
["average"])
1000 self
.assertFalse(ticker
["inverted"])
1002 ticker
= m
.get_ticker("ETH", "XVG")
1003 self
.assertEqual(0.0625, ticker
["average"])
1004 self
.assertTrue(ticker
["inverted"])
1005 self
.assertIn("original", ticker
)
1006 self
.assertEqual(10, ticker
["original"]["bid"])
1007 self
.assertEqual(25, ticker
["original"]["average"])
1009 ticker
= m
.get_ticker("XVG", "XMR")
1010 self
.assertIsNone(ticker
)
1012 with self
.subTest(get_tickers
=False):
1013 self
.ccxt
.fetch_tickers
.return_value
= None
1014 self
.ccxt
.fetch_ticker
.side_effect
= [
1015 { "bid": 1, "ask": 3 }
,
1016 market
.ExchangeError("foo"),
1017 { "bid": 10, "ask": 40 }
,
1018 market
.ExchangeError("foo"),
1019 market
.ExchangeError("foo"),
1022 m
= market
.Market(self
.ccxt
)
1024 ticker
= m
.get_ticker("ETH", "ETC")
1025 self
.ccxt
.fetch_ticker
.assert_called_with("ETH/ETC")
1026 self
.assertEqual(1, ticker
["bid"])
1027 self
.assertEqual(3, ticker
["ask"])
1028 self
.assertEqual(2, ticker
["average"])
1029 self
.assertFalse(ticker
["inverted"])
1031 ticker
= m
.get_ticker("ETH", "XVG")
1032 self
.assertEqual(0.0625, ticker
["average"])
1033 self
.assertTrue(ticker
["inverted"])
1034 self
.assertIn("original", ticker
)
1035 self
.assertEqual(10, ticker
["original"]["bid"])
1036 self
.assertEqual(25, ticker
["original"]["average"])
1038 ticker
= m
.get_ticker("XVG", "XMR")
1039 self
.assertIsNone(ticker
)
1041 def test_fetch_fees(self
):
1042 m
= market
.Market(self
.ccxt
)
1043 self
.ccxt
.fetch_fees
.return_value
= "Foo"
1044 self
.assertEqual("Foo", m
.fetch_fees())
1045 self
.ccxt
.fetch_fees
.assert_called_once()
1046 self
.ccxt
.reset_mock()
1047 self
.assertEqual("Foo", m
.fetch_fees())
1048 self
.ccxt
.fetch_fees
.assert_not_called()
1050 @mock.patch.object(portfolio
.Portfolio
, "repartition")
1051 @mock.patch.object(market
.Market
, "get_ticker")
1052 @mock.patch.object(market
.TradeStore
, "compute_trades")
1053 def test_prepare_trades(self
, compute_trades
, get_ticker
, repartition
):
1054 repartition
.return_value
= {
1055 "XEM": (D("0.75"), "long"),
1056 "BTC": (D("0.25"), "long"),
1058 def _get_ticker(c1
, c2
):
1059 if c1
== "USDT" and c2
== "BTC":
1060 return { "average": D("0.0001") }
1061 if c1
== "XVG" and c2
== "BTC":
1062 return { "average": D("0.000001") }
1063 if c1
== "XEM" and c2
== "BTC":
1064 return { "average": D("0.001") }
1065 self
.fail("Should be called with {}, {}".format(c1
, c2
))
1066 get_ticker
.side_effect
= _get_ticker
1068 with mock
.patch("market.ReportStore"):
1069 m
= market
.Market(self
.ccxt
)
1070 self
.ccxt
.fetch_all_balances
.return_value
= {
1072 "exchange_free": D("10000.0"),
1073 "exchange_used": D("0.0"),
1074 "exchange_total": D("10000.0"),
1075 "total": D("10000.0")
1078 "exchange_free": D("10000.0"),
1079 "exchange_used": D("0.0"),
1080 "exchange_total": D("10000.0"),
1081 "total": D("10000.0")
1085 m
.balances
.fetch_balances(tag
="tag")
1088 compute_trades
.assert_called()
1090 call
= compute_trades
.call_args
1091 self
.assertEqual(1, call
[0][0]["USDT"].value
)
1092 self
.assertEqual(D("0.01"), call
[0][0]["XVG"].value
)
1093 self
.assertEqual(D("0.2525"), call
[0][1]["BTC"].value
)
1094 self
.assertEqual(D("0.7575"), call
[0][1]["XEM"].value
)
1095 m
.report
.log_stage
.assert_called_once_with("prepare_trades",
1096 base_currency
='BTC', compute_value
='average',
1097 liquidity
='medium', only
=None, repartition
=None)
1098 m
.report
.log_balances
.assert_called_once_with(tag
="tag")
1101 @mock.patch.object(portfolio
.time
, "sleep")
1102 @mock.patch.object(market
.TradeStore
, "all_orders")
1103 def test_follow_orders(self
, all_orders
, time_mock
):
1104 for debug
, sleep
in [
1105 (False, None), (True, None),
1106 (False, 12), (True, 12)]:
1107 with self
.subTest(sleep
=sleep
, debug
=debug
), \
1108 mock
.patch("market.ReportStore"):
1109 m
= market
.Market(self
.ccxt
, debug
=debug
)
1111 order_mock1
= mock
.Mock()
1112 order_mock2
= mock
.Mock()
1113 order_mock3
= mock
.Mock()
1114 all_orders
.side_effect
= [
1115 [order_mock1
, order_mock2
],
1116 [order_mock1
, order_mock2
],
1118 [order_mock1
, order_mock3
],
1119 [order_mock1
, order_mock3
],
1121 [order_mock1
, order_mock3
],
1122 [order_mock1
, order_mock3
],
1127 order_mock1
.get_status
.side_effect
= ["open", "open", "closed"]
1128 order_mock2
.get_status
.side_effect
= ["open"]
1129 order_mock3
.get_status
.side_effect
= ["open", "closed"]
1131 order_mock1
.trade
= mock
.Mock()
1132 order_mock2
.trade
= mock
.Mock()
1133 order_mock3
.trade
= mock
.Mock()
1135 m
.follow_orders(sleep
=sleep
)
1137 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 1)
1138 order_mock1
.trade
.update_order
.assert_any_call(order_mock1
, 2)
1139 self
.assertEqual(2, order_mock1
.trade
.update_order
.call_count
)
1140 self
.assertEqual(3, order_mock1
.get_status
.call_count
)
1142 order_mock2
.trade
.update_order
.assert_any_call(order_mock2
, 1)
1143 self
.assertEqual(1, order_mock2
.trade
.update_order
.call_count
)
1144 self
.assertEqual(1, order_mock2
.get_status
.call_count
)
1146 order_mock3
.trade
.update_order
.assert_any_call(order_mock3
, 2)
1147 self
.assertEqual(1, order_mock3
.trade
.update_order
.call_count
)
1148 self
.assertEqual(2, order_mock3
.get_status
.call_count
)
1149 m
.report
.log_stage
.assert_called()
1151 mock
.call("follow_orders_begin"),
1152 mock
.call("follow_orders_tick_1"),
1153 mock
.call("follow_orders_tick_2"),
1154 mock
.call("follow_orders_tick_3"),
1155 mock
.call("follow_orders_end"),
1157 m
.report
.log_stage
.assert_has_calls(calls
)
1158 m
.report
.log_orders
.assert_called()
1159 self
.assertEqual(3, m
.report
.log_orders
.call_count
)
1161 mock
.call([order_mock1
, order_mock2
], tick
=1),
1162 mock
.call([order_mock1
, order_mock3
], tick
=2),
1163 mock
.call([order_mock1
, order_mock3
], tick
=3),
1165 m
.report
.log_orders
.assert_has_calls(calls
)
1167 mock
.call(order_mock1
, 3, finished
=True),
1168 mock
.call(order_mock3
, 3, finished
=True),
1170 m
.report
.log_order
.assert_has_calls(calls
)
1174 m
.report
.log_debug_action
.assert_called_with("Set follow_orders tick to 7s")
1175 time_mock
.assert_called_with(7)
1177 time_mock
.assert_called_with(30)
1179 time_mock
.assert_called_with(sleep
)
1181 @mock.patch.object(market
.BalanceStore
, "fetch_balances")
1182 def test_move_balance(self
, fetch_balances
):
1183 for debug
in [True, False]:
1184 with self
.subTest(debug
=debug
),\
1185 mock
.patch("market.ReportStore"):
1186 m
= market
.Market(self
.ccxt
, debug
=debug
)
1188 value_from
= portfolio
.Amount("BTC", "1.0")
1189 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1190 value_to
= portfolio
.Amount("BTC", "10.0")
1191 trade1
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
1193 value_from
= portfolio
.Amount("BTC", "0.0")
1194 value_from
.linked_to
= portfolio
.Amount("ETH", "0.0")
1195 value_to
= portfolio
.Amount("BTC", "-3.0")
1196 trade2
= portfolio
.Trade(value_from
, value_to
, "ETH", m
)
1198 value_from
= portfolio
.Amount("USDT", "0.0")
1199 value_from
.linked_to
= portfolio
.Amount("XVG", "0.0")
1200 value_to
= portfolio
.Amount("USDT", "-50.0")
1201 trade3
= portfolio
.Trade(value_from
, value_to
, "XVG", m
)
1203 m
.trades
.all
= [trade1
, trade2
, trade3
]
1204 balance1
= portfolio
.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" }
)
1205 balance2
= portfolio
.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" }
)
1206 balance3
= portfolio
.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" }
)
1207 m
.balances
.all
= {"BTC": balance1, "USDT": balance2, "ETC": balance3}
1211 fetch_balances
.assert_called_with()
1212 m
.report
.log_move_balances
.assert_called_once()
1215 m
.report
.log_debug_action
.assert_called()
1216 self
.assertEqual(3, m
.report
.log_debug_action
.call_count
)
1218 self
.ccxt
.transfer_balance
.assert_any_call("BTC", 3, "exchange", "margin")
1219 self
.ccxt
.transfer_balance
.assert_any_call("USDT", 100, "exchange", "margin")
1220 self
.ccxt
.transfer_balance
.assert_any_call("ETC", 5, "margin", "exchange")
1222 def test_store_report(self
):
1224 file_open
= mock
.mock_open()
1225 with self
.subTest(file=None), mock
.patch("market.open", file_open
):
1226 m
= market
.Market(self
.ccxt
, user_id
=1)
1228 file_open
.assert_not_called()
1230 file_open
= mock
.mock_open()
1231 m
= market
.Market(self
.ccxt
, report_path
="present", user_id
=1)
1232 with self
.subTest(file="present"),\
1233 mock
.patch("market.open", file_open
),\
1234 mock
.patch
.object(m
, "report") as report
,\
1235 mock
.patch
.object(market
, "datetime") as time_mock
:
1237 time_mock
.now
.return_value
= datetime
.datetime(2018, 2, 25)
1238 report
.to_json
.return_value
= "json_content"
1242 file_open
.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
1243 file_open().write
.assert_called_once_with("json_content")
1244 m
.report
.to_json
.assert_called_once_with()
1246 m
= market
.Market(self
.ccxt
, report_path
="error", user_id
=1)
1247 with self
.subTest(file="error"),\
1248 mock
.patch("market.open") as file_open
,\
1249 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
1250 file_open
.side_effect
= FileNotFoundError
1254 self
.assertRegex(stdout_mock
.getvalue(), "impossible to store report file: FileNotFoundError;")
1256 def test_print_orders(self
):
1257 m
= market
.Market(self
.ccxt
)
1258 with mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
1259 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
1260 mock
.patch
.object(m
, "prepare_trades") as prepare_trades
,\
1261 mock
.patch
.object(m
.trades
, "prepare_orders") as prepare_orders
:
1264 log_stage
.assert_called_with("print_orders")
1265 fetch_balances
.assert_called_with(tag
="print_orders")
1266 prepare_trades
.assert_called_with(base_currency
="BTC",
1267 compute_value
="average")
1268 prepare_orders
.assert_called_with(compute_value
="average")
1270 def test_print_balances(self
):
1271 m
= market
.Market(self
.ccxt
)
1273 with mock
.patch
.object(m
.balances
, "in_currency") as in_currency
,\
1274 mock
.patch
.object(m
.report
, "log_stage") as log_stage
,\
1275 mock
.patch
.object(m
.balances
, "fetch_balances") as fetch_balances
,\
1276 mock
.patch
.object(m
.report
, "print_log") as print_log
:
1278 in_currency
.return_value
= {
1279 "BTC": portfolio
.Amount("BTC", "0.65"),
1280 "ETH": portfolio
.Amount("BTC", "0.3"),
1285 log_stage
.assert_called_once_with("print_balances")
1286 fetch_balances
.assert_called_with()
1287 print_log
.assert_has_calls([
1288 mock
.call("total:"),
1289 mock
.call(portfolio
.Amount("BTC", "0.95")),
1292 @mock.patch("market.Processor.process")
1293 @mock.patch("market.ReportStore.log_error")
1294 @mock.patch("market.Market.store_report")
1295 def test_process(self
, store_report
, log_error
, process
):
1296 m
= market
.Market(self
.ccxt
)
1297 with self
.subTest(before
=False, after
=False):
1300 process
.assert_not_called()
1301 store_report
.assert_called_once()
1302 log_error
.assert_not_called()
1304 process
.reset_mock()
1305 log_error
.reset_mock()
1306 store_report
.reset_mock()
1307 with self
.subTest(before
=True, after
=False):
1308 m
.process(None, before
=True)
1310 process
.assert_called_once_with("sell_all", steps
="before")
1311 store_report
.assert_called_once()
1312 log_error
.assert_not_called()
1314 process
.reset_mock()
1315 log_error
.reset_mock()
1316 store_report
.reset_mock()
1317 with self
.subTest(before
=False, after
=True):
1318 m
.process(None, after
=True)
1320 process
.assert_called_once_with("sell_all", steps
="after")
1321 store_report
.assert_called_once()
1322 log_error
.assert_not_called()
1324 process
.reset_mock()
1325 log_error
.reset_mock()
1326 store_report
.reset_mock()
1327 with self
.subTest(before
=True, after
=True):
1328 m
.process(None, before
=True, after
=True)
1330 process
.assert_has_calls([
1331 mock
.call("sell_all", steps
="before"),
1332 mock
.call("sell_all", steps
="after"),
1334 store_report
.assert_called_once()
1335 log_error
.assert_not_called()
1337 process
.reset_mock()
1338 log_error
.reset_mock()
1339 store_report
.reset_mock()
1340 with self
.subTest(action
="print_balances"),\
1341 mock
.patch
.object(m
, "print_balances") as print_balances
:
1342 m
.process(["print_balances"])
1344 process
.assert_not_called()
1345 log_error
.assert_not_called()
1346 store_report
.assert_called_once()
1347 print_balances
.assert_called_once_with()
1349 log_error
.reset_mock()
1350 store_report
.reset_mock()
1351 with self
.subTest(action
="print_orders"),\
1352 mock
.patch
.object(m
, "print_orders") as print_orders
,\
1353 mock
.patch
.object(m
, "print_balances") as print_balances
:
1354 m
.process(["print_orders", "print_balances"])
1356 process
.assert_not_called()
1357 log_error
.assert_not_called()
1358 store_report
.assert_called_once()
1359 print_orders
.assert_called_once_with()
1360 print_balances
.assert_called_once_with()
1362 log_error
.reset_mock()
1363 store_report
.reset_mock()
1364 with self
.subTest(action
="unknown"):
1365 m
.process(["unknown"])
1366 log_error
.assert_called_once_with("market_process", message
="Unknown action unknown")
1367 store_report
.assert_called_once()
1369 log_error
.reset_mock()
1370 store_report
.reset_mock()
1371 with self
.subTest(unhandled_exception
=True):
1372 process
.side_effect
= Exception("bouh")
1374 m
.process(None, before
=True)
1375 log_error
.assert_called_with("market_process", exception
=mock
.ANY
)
1376 store_report
.assert_called_once()
1378 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1379 class TradeStoreTest(WebMockTestCase
):
1380 def test_compute_trades(self
):
1381 self
.m
.balances
.currencies
.return_value
= ["XMR", "DASH", "XVG", "BTC", "ETH"]
1384 "XMR": portfolio
.Amount("BTC", D("0.9")),
1385 "DASH": portfolio
.Amount("BTC", D("0.4")),
1386 "XVG": portfolio
.Amount("BTC", D("-0.5")),
1387 "BTC": portfolio
.Amount("BTC", D("0.5")),
1390 "DASH": portfolio
.Amount("BTC", D("0.5")),
1391 "XVG": portfolio
.Amount("BTC", D("0.1")),
1392 "BTC": portfolio
.Amount("BTC", D("0.4")),
1393 "ETH": portfolio
.Amount("BTC", D("0.3")),
1403 with mock
.patch
.object(market
.TradeStore
, "trade_if_matching") as trade_if_matching
:
1404 trade_store
= market
.TradeStore(self
.m
)
1405 trade_if_matching
.side_effect
= side_effect
1407 trade_store
.compute_trades(values_in_base
,
1408 new_repartition
, only
="only")
1410 self
.assertEqual(5, trade_if_matching
.call_count
)
1411 self
.assertEqual(3, len(trade_store
.all
))
1412 self
.assertEqual([1, 4, 5], trade_store
.all
)
1413 self
.m
.report
.log_trades
.assert_called_with(side_effect
, "only")
1415 def test_trade_if_matching(self
):
1417 with self
.subTest(only
="nope"):
1418 trade_store
= market
.TradeStore(self
.m
)
1419 result
= trade_store
.trade_if_matching(
1420 portfolio
.Amount("BTC", D("0")),
1421 portfolio
.Amount("BTC", D("0.3")),
1423 self
.assertEqual(False, result
[0])
1424 self
.assertIsInstance(result
[1], portfolio
.Trade
)
1426 with self
.subTest(only
=None):
1427 trade_store
= market
.TradeStore(self
.m
)
1428 result
= trade_store
.trade_if_matching(
1429 portfolio
.Amount("BTC", D("0")),
1430 portfolio
.Amount("BTC", D("0.3")),
1432 self
.assertEqual(True, result
[0])
1434 with self
.subTest(only
="acquire"):
1435 trade_store
= market
.TradeStore(self
.m
)
1436 result
= trade_store
.trade_if_matching(
1437 portfolio
.Amount("BTC", D("0")),
1438 portfolio
.Amount("BTC", D("0.3")),
1439 "ETH", only
="acquire")
1440 self
.assertEqual(True, result
[0])
1442 with self
.subTest(only
="dispose"):
1443 trade_store
= market
.TradeStore(self
.m
)
1444 result
= trade_store
.trade_if_matching(
1445 portfolio
.Amount("BTC", D("0")),
1446 portfolio
.Amount("BTC", D("0.3")),
1447 "ETH", only
="dispose")
1448 self
.assertEqual(False, result
[0])
1450 def test_prepare_orders(self
):
1451 trade_store
= market
.TradeStore(self
.m
)
1453 trade_mock1
= mock
.Mock()
1454 trade_mock2
= mock
.Mock()
1455 trade_mock3
= mock
.Mock()
1457 trade_mock1
.prepare_order
.return_value
= 1
1458 trade_mock2
.prepare_order
.return_value
= 2
1459 trade_mock3
.prepare_order
.return_value
= 3
1461 trade_mock1
.pending
= True
1462 trade_mock2
.pending
= True
1463 trade_mock3
.pending
= False
1465 trade_store
.all
.append(trade_mock1
)
1466 trade_store
.all
.append(trade_mock2
)
1467 trade_store
.all
.append(trade_mock3
)
1469 trade_store
.prepare_orders()
1470 trade_mock1
.prepare_order
.assert_called_with(compute_value
="default")
1471 trade_mock2
.prepare_order
.assert_called_with(compute_value
="default")
1472 trade_mock3
.prepare_order
.assert_not_called()
1473 self
.m
.report
.log_orders
.assert_called_once_with([1, 2], None, "default")
1475 self
.m
.report
.log_orders
.reset_mock()
1477 trade_store
.prepare_orders(compute_value
="bla")
1478 trade_mock1
.prepare_order
.assert_called_with(compute_value
="bla")
1479 trade_mock2
.prepare_order
.assert_called_with(compute_value
="bla")
1480 self
.m
.report
.log_orders
.assert_called_once_with([1, 2], None, "bla")
1482 trade_mock1
.prepare_order
.reset_mock()
1483 trade_mock2
.prepare_order
.reset_mock()
1484 self
.m
.report
.log_orders
.reset_mock()
1486 trade_mock1
.action
= "foo"
1487 trade_mock2
.action
= "bar"
1488 trade_store
.prepare_orders(only
="bar")
1489 trade_mock1
.prepare_order
.assert_not_called()
1490 trade_mock2
.prepare_order
.assert_called_with(compute_value
="default")
1491 self
.m
.report
.log_orders
.assert_called_once_with([2], "bar", "default")
1493 def test_print_all_with_order(self
):
1494 trade_mock1
= mock
.Mock()
1495 trade_mock2
= mock
.Mock()
1496 trade_mock3
= mock
.Mock()
1497 trade_store
= market
.TradeStore(self
.m
)
1498 trade_store
.all
= [trade_mock1
, trade_mock2
, trade_mock3
]
1500 trade_store
.print_all_with_order()
1502 trade_mock1
.print_with_order
.assert_called()
1503 trade_mock2
.print_with_order
.assert_called()
1504 trade_mock3
.print_with_order
.assert_called()
1506 def test_run_orders(self
):
1507 with mock
.patch
.object(market
.TradeStore
, "all_orders") as all_orders
:
1508 order_mock1
= mock
.Mock()
1509 order_mock2
= mock
.Mock()
1510 order_mock3
= mock
.Mock()
1511 trade_store
= market
.TradeStore(self
.m
)
1513 all_orders
.return_value
= [order_mock1
, order_mock2
, order_mock3
]
1515 trade_store
.run_orders()
1517 all_orders
.assert_called_with(state
="pending")
1519 order_mock1
.run
.assert_called()
1520 order_mock2
.run
.assert_called()
1521 order_mock3
.run
.assert_called()
1523 self
.m
.report
.log_stage
.assert_called_with("run_orders")
1524 self
.m
.report
.log_orders
.assert_called_with([order_mock1
, order_mock2
,
1527 def test_all_orders(self
):
1528 trade_mock1
= mock
.Mock()
1529 trade_mock2
= mock
.Mock()
1531 order_mock1
= mock
.Mock()
1532 order_mock2
= mock
.Mock()
1533 order_mock3
= mock
.Mock()
1535 trade_mock1
.orders
= [order_mock1
, order_mock2
]
1536 trade_mock2
.orders
= [order_mock3
]
1538 order_mock1
.status
= "pending"
1539 order_mock2
.status
= "open"
1540 order_mock3
.status
= "open"
1542 trade_store
= market
.TradeStore(self
.m
)
1543 trade_store
.all
.append(trade_mock1
)
1544 trade_store
.all
.append(trade_mock2
)
1546 orders
= trade_store
.all_orders()
1547 self
.assertEqual(3, len(orders
))
1549 open_orders
= trade_store
.all_orders(state
="open")
1550 self
.assertEqual(2, len(open_orders
))
1551 self
.assertEqual([order_mock2
, order_mock3
], open_orders
)
1553 def test_update_all_orders_status(self
):
1554 with mock
.patch
.object(market
.TradeStore
, "all_orders") as all_orders
:
1555 order_mock1
= mock
.Mock()
1556 order_mock2
= mock
.Mock()
1557 order_mock3
= mock
.Mock()
1559 all_orders
.return_value
= [order_mock1
, order_mock2
, order_mock3
]
1561 trade_store
= market
.TradeStore(self
.m
)
1563 trade_store
.update_all_orders_status()
1564 all_orders
.assert_called_with(state
="open")
1566 order_mock1
.get_status
.assert_called()
1567 order_mock2
.get_status
.assert_called()
1568 order_mock3
.get_status
.assert_called()
1570 def test_close_trades(self
):
1571 trade_mock1
= mock
.Mock()
1572 trade_mock2
= mock
.Mock()
1573 trade_mock3
= mock
.Mock()
1575 trade_store
= market
.TradeStore(self
.m
)
1577 trade_store
.all
.append(trade_mock1
)
1578 trade_store
.all
.append(trade_mock2
)
1579 trade_store
.all
.append(trade_mock3
)
1581 trade_store
.close_trades()
1583 trade_mock1
.close
.assert_called_once_with()
1584 trade_mock2
.close
.assert_called_once_with()
1585 trade_mock3
.close
.assert_called_once_with()
1587 def test_pending(self
):
1588 trade_mock1
= mock
.Mock()
1589 trade_mock1
.pending
= True
1590 trade_mock2
= mock
.Mock()
1591 trade_mock2
.pending
= True
1592 trade_mock3
= mock
.Mock()
1593 trade_mock3
.pending
= False
1595 trade_store
= market
.TradeStore(self
.m
)
1597 trade_store
.all
.append(trade_mock1
)
1598 trade_store
.all
.append(trade_mock2
)
1599 trade_store
.all
.append(trade_mock3
)
1601 self
.assertEqual([trade_mock1
, trade_mock2
], trade_store
.pending
)
1603 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1604 class BalanceStoreTest(WebMockTestCase
):
1606 super(BalanceStoreTest
, self
).setUp()
1608 self
.fetch_balance
= {
1612 "exchange_total": 0,
1616 "exchange_free": D("6.0"),
1617 "exchange_used": D("1.2"),
1618 "exchange_total": D("7.2"),
1622 "exchange_free": 16,
1624 "exchange_total": 16,
1630 "exchange_total": 0,
1631 "margin_total": D("-1.0"),
1636 def test_in_currency(self
):
1637 self
.m
.get_ticker
.return_value
= {
1640 "average": D("0.1"),
1643 balance_store
= market
.BalanceStore(self
.m
)
1644 balance_store
.all
= {
1645 "BTC": portfolio
.Balance("BTC", {
1647 "exchange_total":"0.65",
1648 "exchange_free": "0.35",
1649 "exchange_used": "0.30"}),
1650 "ETH": portfolio
.Balance("ETH", {
1652 "exchange_total": 3,
1654 "exchange_used": 0}),
1657 amounts
= balance_store
.in_currency("BTC")
1658 self
.assertEqual("BTC", amounts
["ETH"].currency
)
1659 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
1660 self
.assertEqual(D("0.30"), amounts
["ETH"].value
)
1661 self
.m
.report
.log_tickers
.assert_called_once_with(amounts
, "BTC",
1663 self
.m
.report
.log_tickers
.reset_mock()
1665 amounts
= balance_store
.in_currency("BTC", compute_value
="bid")
1666 self
.assertEqual(D("0.65"), amounts
["BTC"].value
)
1667 self
.assertEqual(D("0.27"), amounts
["ETH"].value
)
1668 self
.m
.report
.log_tickers
.assert_called_once_with(amounts
, "BTC",
1670 self
.m
.report
.log_tickers
.reset_mock()
1672 amounts
= balance_store
.in_currency("BTC", compute_value
="bid", type="exchange_used")
1673 self
.assertEqual(D("0.30"), amounts
["BTC"].value
)
1674 self
.assertEqual(0, amounts
["ETH"].value
)
1675 self
.m
.report
.log_tickers
.assert_called_once_with(amounts
, "BTC",
1676 "bid", "exchange_used")
1677 self
.m
.report
.log_tickers
.reset_mock()
1679 def test_fetch_balances(self
):
1680 self
.m
.ccxt
.fetch_all_balances
.return_value
= self
.fetch_balance
1682 balance_store
= market
.BalanceStore(self
.m
)
1684 balance_store
.fetch_balances()
1685 self
.assertNotIn("ETC", balance_store
.currencies())
1686 self
.assertListEqual(["USDT", "XVG", "XMR"], list(balance_store
.currencies()))
1688 balance_store
.all
["ETC"] = portfolio
.Balance("ETC", {
1689 "exchange_total": "1", "exchange_free": "0",
1690 "exchange_used": "1" })
1691 balance_store
.fetch_balances(tag
="foo")
1692 self
.assertEqual(0, balance_store
.all
["ETC"].total
)
1693 self
.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store
.currencies()))
1694 self
.m
.report
.log_balances
.assert_called_with(tag
="foo")
1696 @mock.patch.object(portfolio
.Portfolio
, "repartition")
1697 def test_dispatch_assets(self
, repartition
):
1698 self
.m
.ccxt
.fetch_all_balances
.return_value
= self
.fetch_balance
1700 balance_store
= market
.BalanceStore(self
.m
)
1701 balance_store
.fetch_balances()
1703 self
.assertNotIn("XEM", balance_store
.currencies())
1705 repartition_hash
= {
1706 "XEM": (D("0.75"), "long"),
1707 "BTC": (D("0.26"), "long"),
1708 "DASH": (D("0.10"), "short"),
1710 repartition
.return_value
= repartition_hash
1712 amounts
= balance_store
.dispatch_assets(portfolio
.Amount("BTC", "11.1"))
1713 repartition
.assert_called_with(self
.m
, liquidity
="medium")
1714 self
.assertIn("XEM", balance_store
.currencies())
1715 self
.assertEqual(D("2.6"), amounts
["BTC"].value
)
1716 self
.assertEqual(D("7.5"), amounts
["XEM"].value
)
1717 self
.assertEqual(D("-1.0"), amounts
["DASH"].value
)
1718 self
.m
.report
.log_balances
.assert_called_with(tag
=None)
1719 self
.m
.report
.log_dispatch
.assert_called_once_with(portfolio
.Amount("BTC",
1720 "11.1"), amounts
, "medium", repartition_hash
)
1722 def test_currencies(self
):
1723 balance_store
= market
.BalanceStore(self
.m
)
1725 balance_store
.all
= {
1726 "BTC": portfolio
.Balance("BTC", {
1728 "exchange_total":"0.65",
1729 "exchange_free": "0.35",
1730 "exchange_used": "0.30"}),
1731 "ETH": portfolio
.Balance("ETH", {
1733 "exchange_total": 3,
1735 "exchange_used": 0}),
1737 self
.assertListEqual(["BTC", "ETH"], list(balance_store
.currencies()))
1739 def test_as_json(self
):
1740 balance_mock1
= mock
.Mock()
1741 balance_mock1
.as_json
.return_value
= 1
1743 balance_mock2
= mock
.Mock()
1744 balance_mock2
.as_json
.return_value
= 2
1746 balance_store
= market
.BalanceStore(self
.m
)
1747 balance_store
.all
= {
1748 "BTC": balance_mock1
,
1749 "ETH": balance_mock2
,
1752 as_json
= balance_store
.as_json()
1753 self
.assertEqual(1, as_json
["BTC"])
1754 self
.assertEqual(2, as_json
["ETH"])
1757 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1758 class ComputationTest(WebMockTestCase
):
1759 def test_compute_value(self
):
1760 compute
= mock
.Mock()
1761 portfolio
.Computation
.compute_value("foo", "buy", compute_value
=compute
)
1762 compute
.assert_called_with("foo", "ask")
1764 compute
.reset_mock()
1765 portfolio
.Computation
.compute_value("foo", "sell", compute_value
=compute
)
1766 compute
.assert_called_with("foo", "bid")
1768 compute
.reset_mock()
1769 portfolio
.Computation
.compute_value("foo", "ask", compute_value
=compute
)
1770 compute
.assert_called_with("foo", "ask")
1772 compute
.reset_mock()
1773 portfolio
.Computation
.compute_value("foo", "bid", compute_value
=compute
)
1774 compute
.assert_called_with("foo", "bid")
1776 compute
.reset_mock()
1777 portfolio
.Computation
.computations
["test"] = compute
1778 portfolio
.Computation
.compute_value("foo", "bid", compute_value
="test")
1779 compute
.assert_called_with("foo", "bid")
1782 @unittest.skipUnless("unit" in limits
, "Unit skipped")
1783 class TradeTest(WebMockTestCase
):
1785 def test_values_assertion(self
):
1786 value_from
= portfolio
.Amount("BTC", "1.0")
1787 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1788 value_to
= portfolio
.Amount("BTC", "1.0")
1789 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1790 self
.assertEqual("BTC", trade
.base_currency
)
1791 self
.assertEqual("ETH", trade
.currency
)
1792 self
.assertEqual(self
.m
, trade
.market
)
1794 with self
.assertRaises(AssertionError):
1795 portfolio
.Trade(value_from
, -value_to
, "ETH", self
.m
)
1796 with self
.assertRaises(AssertionError):
1797 portfolio
.Trade(value_from
, value_to
, "ETC", self
.m
)
1798 with self
.assertRaises(AssertionError):
1799 value_from
.currency
= "ETH"
1800 portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1801 value_from
.currency
= "BTC"
1802 with self
.assertRaises(AssertionError):
1803 value_from2
= portfolio
.Amount("BTC", "1.0")
1804 portfolio
.Trade(value_from2
, value_to
, "ETH", self
.m
)
1806 value_from
= portfolio
.Amount("BTC", 0)
1807 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1808 self
.assertEqual(0, trade
.value_from
.linked_to
)
1810 def test_action(self
):
1811 value_from
= portfolio
.Amount("BTC", "1.0")
1812 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1813 value_to
= portfolio
.Amount("BTC", "1.0")
1814 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1816 self
.assertIsNone(trade
.action
)
1818 value_from
= portfolio
.Amount("BTC", "1.0")
1819 value_from
.linked_to
= portfolio
.Amount("BTC", "1.0")
1820 value_to
= portfolio
.Amount("BTC", "2.0")
1821 trade
= portfolio
.Trade(value_from
, value_to
, "BTC", self
.m
)
1823 self
.assertIsNone(trade
.action
)
1825 value_from
= portfolio
.Amount("BTC", "0.5")
1826 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1827 value_to
= portfolio
.Amount("BTC", "1.0")
1828 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1830 self
.assertEqual("acquire", trade
.action
)
1832 value_from
= portfolio
.Amount("BTC", "0")
1833 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1834 value_to
= portfolio
.Amount("BTC", "-1.0")
1835 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1837 self
.assertEqual("acquire", trade
.action
)
1839 def test_order_action(self
):
1840 value_from
= portfolio
.Amount("BTC", "0.5")
1841 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1842 value_to
= portfolio
.Amount("BTC", "1.0")
1843 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1845 self
.assertEqual("buy", trade
.order_action(False))
1846 self
.assertEqual("sell", trade
.order_action(True))
1848 value_from
= portfolio
.Amount("BTC", "0")
1849 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1850 value_to
= portfolio
.Amount("BTC", "-1.0")
1851 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1853 self
.assertEqual("sell", trade
.order_action(False))
1854 self
.assertEqual("buy", trade
.order_action(True))
1856 def test_trade_type(self
):
1857 value_from
= portfolio
.Amount("BTC", "0.5")
1858 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1859 value_to
= portfolio
.Amount("BTC", "1.0")
1860 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1862 self
.assertEqual("long", trade
.trade_type
)
1864 value_from
= portfolio
.Amount("BTC", "0")
1865 value_from
.linked_to
= portfolio
.Amount("ETH", "0")
1866 value_to
= portfolio
.Amount("BTC", "-1.0")
1867 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1869 self
.assertEqual("short", trade
.trade_type
)
1871 def test_is_fullfiled(self
):
1872 value_from
= portfolio
.Amount("BTC", "0.5")
1873 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1874 value_to
= portfolio
.Amount("BTC", "1.0")
1875 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1877 order1
= mock
.Mock()
1878 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.3")
1880 order2
= mock
.Mock()
1881 order2
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.01")
1882 trade
.orders
.append(order1
)
1883 trade
.orders
.append(order2
)
1885 self
.assertFalse(trade
.is_fullfiled
)
1887 order3
= mock
.Mock()
1888 order3
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.19")
1889 trade
.orders
.append(order3
)
1891 self
.assertTrue(trade
.is_fullfiled
)
1893 def test_filled_amount(self
):
1894 value_from
= portfolio
.Amount("BTC", "0.5")
1895 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
1896 value_to
= portfolio
.Amount("BTC", "1.0")
1897 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
1899 order1
= mock
.Mock()
1900 order1
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.3")
1902 order2
= mock
.Mock()
1903 order2
.filled_amount
.return_value
= portfolio
.Amount("ETH", "0.01")
1904 trade
.orders
.append(order1
)
1905 trade
.orders
.append(order2
)
1907 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount())
1908 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
1909 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
1911 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=False))
1912 order1
.filled_amount
.assert_called_with(in_base_currency
=False)
1913 order2
.filled_amount
.assert_called_with(in_base_currency
=False)
1915 self
.assertEqual(portfolio
.Amount("ETH", "0.31"), trade
.filled_amount(in_base_currency
=True))
1916 order1
.filled_amount
.assert_called_with(in_base_currency
=True)
1917 order2
.filled_amount
.assert_called_with(in_base_currency
=True)
1919 @mock.patch.object(portfolio
.Computation
, "compute_value")
1920 @mock.patch.object(portfolio
.Trade
, "filled_amount")
1921 @mock.patch.object(portfolio
, "Order")
1922 def test_prepare_order(self
, Order
, filled_amount
, compute_value
):
1923 Order
.return_value
= "Order"
1925 with self
.subTest(desc
="Nothing to do"):
1926 value_from
= portfolio
.Amount("BTC", "10")
1927 value_from
.rate
= D("0.1")
1928 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1929 value_to
= portfolio
.Amount("BTC", "10")
1930 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
1932 trade
.prepare_order()
1934 filled_amount
.assert_not_called()
1935 compute_value
.assert_not_called()
1936 self
.assertEqual(0, len(trade
.orders
))
1937 Order
.assert_not_called()
1939 self
.m
.get_ticker
.return_value
= { "inverted": False }
1940 with self
.subTest(desc
="Already filled"):
1941 filled_amount
.return_value
= portfolio
.Amount("FOO", "100")
1942 compute_value
.return_value
= D("0.125")
1944 value_from
= portfolio
.Amount("BTC", "10")
1945 value_from
.rate
= D("0.1")
1946 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1947 value_to
= portfolio
.Amount("BTC", "0")
1948 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
1950 trade
.prepare_order()
1952 filled_amount
.assert_called_with(in_base_currency
=False)
1953 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
1954 self
.assertEqual(0, len(trade
.orders
))
1955 self
.m
.report
.log_error
.assert_called_with("prepare_order", message
=mock
.ANY
)
1956 Order
.assert_not_called()
1958 with self
.subTest(action
="dispose", inverted
=False):
1959 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
1960 compute_value
.return_value
= D("0.125")
1962 value_from
= portfolio
.Amount("BTC", "10")
1963 value_from
.rate
= D("0.1")
1964 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1965 value_to
= portfolio
.Amount("BTC", "1")
1966 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
1968 trade
.prepare_order()
1970 filled_amount
.assert_called_with(in_base_currency
=False)
1971 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
1972 self
.assertEqual(1, len(trade
.orders
))
1973 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
1974 D("0.125"), "BTC", "long", self
.m
,
1975 trade
, close_if_possible
=False)
1977 with self
.subTest(action
="dispose", inverted
=False, close_if_possible
=True):
1978 filled_amount
.return_value
= portfolio
.Amount("FOO", "60")
1979 compute_value
.return_value
= D("0.125")
1981 value_from
= portfolio
.Amount("BTC", "10")
1982 value_from
.rate
= D("0.1")
1983 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
1984 value_to
= portfolio
.Amount("BTC", "1")
1985 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
1987 trade
.prepare_order(close_if_possible
=True)
1989 filled_amount
.assert_called_with(in_base_currency
=False)
1990 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
1991 self
.assertEqual(1, len(trade
.orders
))
1992 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 30),
1993 D("0.125"), "BTC", "long", self
.m
,
1994 trade
, close_if_possible
=True)
1996 with self
.subTest(action
="acquire", inverted
=False):
1997 filled_amount
.return_value
= portfolio
.Amount("BTC", "3")
1998 compute_value
.return_value
= D("0.125")
2000 value_from
= portfolio
.Amount("BTC", "1")
2001 value_from
.rate
= D("0.1")
2002 value_from
.linked_to
= portfolio
.Amount("FOO", "10")
2003 value_to
= portfolio
.Amount("BTC", "10")
2004 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
2006 trade
.prepare_order()
2008 filled_amount
.assert_called_with(in_base_currency
=True)
2009 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "buy", compute_value
="default")
2010 self
.assertEqual(1, len(trade
.orders
))
2012 Order
.assert_called_with("buy", portfolio
.Amount("FOO", 48),
2013 D("0.125"), "BTC", "long", self
.m
,
2014 trade
, close_if_possible
=False)
2016 with self
.subTest(close_if_possible
=True):
2017 filled_amount
.return_value
= portfolio
.Amount("FOO", "0")
2018 compute_value
.return_value
= D("0.125")
2020 value_from
= portfolio
.Amount("BTC", "10")
2021 value_from
.rate
= D("0.1")
2022 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
2023 value_to
= portfolio
.Amount("BTC", "0")
2024 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
2026 trade
.prepare_order()
2028 filled_amount
.assert_called_with(in_base_currency
=False)
2029 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
, "sell", compute_value
="default")
2030 self
.assertEqual(1, len(trade
.orders
))
2031 Order
.assert_called_with("sell", portfolio
.Amount("FOO", 100),
2032 D("0.125"), "BTC", "long", self
.m
,
2033 trade
, close_if_possible
=True)
2035 self
.m
.get_ticker
.return_value
= { "inverted": True, "original": {}
}
2036 with self
.subTest(action
="dispose", inverted
=True):
2037 filled_amount
.return_value
= portfolio
.Amount("FOO", "300")
2038 compute_value
.return_value
= D("125")
2040 value_from
= portfolio
.Amount("BTC", "10")
2041 value_from
.rate
= D("0.01")
2042 value_from
.linked_to
= portfolio
.Amount("FOO", "1000")
2043 value_to
= portfolio
.Amount("BTC", "1")
2044 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
2046 trade
.prepare_order(compute_value
="foo")
2048 filled_amount
.assert_called_with(in_base_currency
=True)
2049 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "buy", compute_value
="foo")
2050 self
.assertEqual(1, len(trade
.orders
))
2051 Order
.assert_called_with("buy", portfolio
.Amount("BTC", D("4.8")),
2052 D("125"), "FOO", "long", self
.m
,
2053 trade
, close_if_possible
=False)
2055 with self
.subTest(action
="acquire", inverted
=True):
2056 filled_amount
.return_value
= portfolio
.Amount("BTC", "4")
2057 compute_value
.return_value
= D("125")
2059 value_from
= portfolio
.Amount("BTC", "1")
2060 value_from
.rate
= D("0.01")
2061 value_from
.linked_to
= portfolio
.Amount("FOO", "100")
2062 value_to
= portfolio
.Amount("BTC", "10")
2063 trade
= portfolio
.Trade(value_from
, value_to
, "FOO", self
.m
)
2065 trade
.prepare_order(compute_value
="foo")
2067 filled_amount
.assert_called_with(in_base_currency
=False)
2068 compute_value
.assert_called_with(self
.m
.get_ticker
.return_value
["original"], "sell", compute_value
="foo")
2069 self
.assertEqual(1, len(trade
.orders
))
2070 Order
.assert_called_with("sell", portfolio
.Amount("BTC", D("5")),
2071 D("125"), "FOO", "long", self
.m
,
2072 trade
, close_if_possible
=False)
2075 @mock.patch.object(portfolio
.Trade
, "prepare_order")
2076 def test_update_order(self
, prepare_order
):
2077 order_mock
= mock
.Mock()
2078 new_order_mock
= mock
.Mock()
2080 value_from
= portfolio
.Amount("BTC", "0.5")
2081 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2082 value_to
= portfolio
.Amount("BTC", "1.0")
2083 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2084 prepare_order
.return_value
= new_order_mock
2086 for i
in [0, 1, 3, 4, 6]:
2087 with self
.subTest(tick
=i
):
2088 trade
.update_order(order_mock
, i
)
2089 order_mock
.cancel
.assert_not_called()
2090 new_order_mock
.run
.assert_not_called()
2091 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
,
2092 update
="waiting", compute_value
=None, new_order
=None)
2094 order_mock
.reset_mock()
2095 new_order_mock
.reset_mock()
2097 self
.m
.report
.log_order
.reset_mock()
2099 trade
.update_order(order_mock
, 2)
2100 order_mock
.cancel
.assert_called()
2101 new_order_mock
.run
.assert_called()
2102 prepare_order
.assert_called()
2103 self
.m
.report
.log_order
.assert_called()
2104 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
2106 mock
.call(order_mock
, 2, update
="adjusting",
2107 compute_value
=mock
.ANY
,
2108 new_order
=new_order_mock
),
2109 mock
.call(order_mock
, 2, new_order
=new_order_mock
),
2111 self
.m
.report
.log_order
.assert_has_calls(calls
)
2113 order_mock
.reset_mock()
2114 new_order_mock
.reset_mock()
2116 self
.m
.report
.log_order
.reset_mock()
2118 trade
.update_order(order_mock
, 5)
2119 order_mock
.cancel
.assert_called()
2120 new_order_mock
.run
.assert_called()
2121 prepare_order
.assert_called()
2122 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
2123 self
.m
.report
.log_order
.assert_called()
2125 mock
.call(order_mock
, 5, update
="adjusting",
2126 compute_value
=mock
.ANY
,
2127 new_order
=new_order_mock
),
2128 mock
.call(order_mock
, 5, new_order
=new_order_mock
),
2130 self
.m
.report
.log_order
.assert_has_calls(calls
)
2132 order_mock
.reset_mock()
2133 new_order_mock
.reset_mock()
2135 self
.m
.report
.log_order
.reset_mock()
2137 trade
.update_order(order_mock
, 7)
2138 order_mock
.cancel
.assert_called()
2139 new_order_mock
.run
.assert_called()
2140 prepare_order
.assert_called_with(compute_value
="default")
2141 self
.m
.report
.log_order
.assert_called()
2142 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
2144 mock
.call(order_mock
, 7, update
="market_fallback",
2145 compute_value
='default',
2146 new_order
=new_order_mock
),
2147 mock
.call(order_mock
, 7, new_order
=new_order_mock
),
2149 self
.m
.report
.log_order
.assert_has_calls(calls
)
2151 order_mock
.reset_mock()
2152 new_order_mock
.reset_mock()
2154 self
.m
.report
.log_order
.reset_mock()
2156 for i
in [10, 13, 16]:
2157 with self
.subTest(tick
=i
):
2158 trade
.update_order(order_mock
, i
)
2159 order_mock
.cancel
.assert_called()
2160 new_order_mock
.run
.assert_called()
2161 prepare_order
.assert_called_with(compute_value
="default")
2162 self
.m
.report
.log_order
.assert_called()
2163 self
.assertEqual(2, self
.m
.report
.log_order
.call_count
)
2165 mock
.call(order_mock
, i
, update
="market_adjust",
2166 compute_value
='default',
2167 new_order
=new_order_mock
),
2168 mock
.call(order_mock
, i
, new_order
=new_order_mock
),
2170 self
.m
.report
.log_order
.assert_has_calls(calls
)
2172 order_mock
.reset_mock()
2173 new_order_mock
.reset_mock()
2175 self
.m
.report
.log_order
.reset_mock()
2177 for i
in [8, 9, 11, 12]:
2178 with self
.subTest(tick
=i
):
2179 trade
.update_order(order_mock
, i
)
2180 order_mock
.cancel
.assert_not_called()
2181 new_order_mock
.run
.assert_not_called()
2182 self
.m
.report
.log_order
.assert_called_once_with(order_mock
, i
, update
="waiting",
2183 compute_value
=None, new_order
=None)
2185 order_mock
.reset_mock()
2186 new_order_mock
.reset_mock()
2188 self
.m
.report
.log_order
.reset_mock()
2191 def test_print_with_order(self
):
2192 value_from
= portfolio
.Amount("BTC", "0.5")
2193 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2194 value_to
= portfolio
.Amount("BTC", "1.0")
2195 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2197 order_mock1
= mock
.Mock()
2198 order_mock1
.__repr__
= mock
.Mock()
2199 order_mock1
.__repr__
.return_value
= "Mock 1"
2200 order_mock2
= mock
.Mock()
2201 order_mock2
.__repr__
= mock
.Mock()
2202 order_mock2
.__repr__
.return_value
= "Mock 2"
2203 order_mock1
.mouvements
= []
2204 mouvement_mock1
= mock
.Mock()
2205 mouvement_mock1
.__repr__
= mock
.Mock()
2206 mouvement_mock1
.__repr__
.return_value
= "Mouvement 1"
2207 mouvement_mock2
= mock
.Mock()
2208 mouvement_mock2
.__repr__
= mock
.Mock()
2209 mouvement_mock2
.__repr__
.return_value
= "Mouvement 2"
2210 order_mock2
.mouvements
= [
2211 mouvement_mock1
, mouvement_mock2
2213 trade
.orders
.append(order_mock1
)
2214 trade
.orders
.append(order_mock2
)
2216 with mock
.patch
.object(trade
, "filled_amount") as filled
:
2217 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
2219 trade
.print_with_order()
2221 self
.m
.report
.print_log
.assert_called()
2222 calls
= self
.m
.report
.print_log
.mock_calls
2223 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls
[0][1][0]))
2224 self
.assertEqual("\tMock 1", str(calls
[1][1][0]))
2225 self
.assertEqual("\tMock 2", str(calls
[2][1][0]))
2226 self
.assertEqual("\t\tMouvement 1", str(calls
[3][1][0]))
2227 self
.assertEqual("\t\tMouvement 2", str(calls
[4][1][0]))
2229 self
.m
.report
.print_log
.reset_mock()
2231 filled
.return_value
= portfolio
.Amount("BTC", "0.5")
2232 trade
.print_with_order()
2233 calls
= self
.m
.report
.print_log
.mock_calls
2234 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ✔)", str(calls
[0][1][0]))
2236 self
.m
.report
.print_log
.reset_mock()
2238 filled
.return_value
= portfolio
.Amount("BTC", "0.1")
2240 trade
.print_with_order()
2241 calls
= self
.m
.report
.print_log
.mock_calls
2242 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls
[0][1][0]))
2244 def test_close(self
):
2245 value_from
= portfolio
.Amount("BTC", "0.5")
2246 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2247 value_to
= portfolio
.Amount("BTC", "1.0")
2248 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2249 order1
= mock
.Mock()
2250 trade
.orders
.append(order1
)
2254 self
.assertEqual(True, trade
.closed
)
2255 order1
.cancel
.assert_called_once_with()
2257 def test_pending(self
):
2258 value_from
= portfolio
.Amount("BTC", "0.5")
2259 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2260 value_to
= portfolio
.Amount("BTC", "1.0")
2261 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2264 self
.assertEqual(False, trade
.pending
)
2266 trade
.closed
= False
2267 self
.assertEqual(True, trade
.pending
)
2269 order1
= mock
.Mock()
2270 order1
.filled_amount
.return_value
= portfolio
.Amount("BTC", "0.5")
2271 trade
.orders
.append(order1
)
2272 self
.assertEqual(False, trade
.pending
)
2274 def test__repr(self
):
2275 value_from
= portfolio
.Amount("BTC", "0.5")
2276 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2277 value_to
= portfolio
.Amount("BTC", "1.0")
2278 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2280 self
.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade
))
2282 def test_as_json(self
):
2283 value_from
= portfolio
.Amount("BTC", "0.5")
2284 value_from
.linked_to
= portfolio
.Amount("ETH", "10.0")
2285 value_to
= portfolio
.Amount("BTC", "1.0")
2286 trade
= portfolio
.Trade(value_from
, value_to
, "ETH", self
.m
)
2288 as_json
= trade
.as_json()
2289 self
.assertEqual("acquire", as_json
["action"])
2290 self
.assertEqual(D("0.5"), as_json
["from"])
2291 self
.assertEqual(D("1.0"), as_json
["to"])
2292 self
.assertEqual("ETH", as_json
["currency"])
2293 self
.assertEqual("BTC", as_json
["base_currency"])
2295 @unittest.skipUnless("unit" in limits
, "Unit skipped")
2296 class OrderTest(WebMockTestCase
):
2297 def test_values(self
):
2298 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2299 D("0.1"), "BTC", "long", "market", "trade")
2300 self
.assertEqual("buy", order
.action
)
2301 self
.assertEqual(10, order
.amount
.value
)
2302 self
.assertEqual("ETH", order
.amount
.currency
)
2303 self
.assertEqual(D("0.1"), order
.rate
)
2304 self
.assertEqual("BTC", order
.base_currency
)
2305 self
.assertEqual("market", order
.market
)
2306 self
.assertEqual("long", order
.trade_type
)
2307 self
.assertEqual("pending", order
.status
)
2308 self
.assertEqual("trade", order
.trade
)
2309 self
.assertIsNone(order
.id)
2310 self
.assertFalse(order
.close_if_possible
)
2312 def test__repr(self
):
2313 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2314 D("0.1"), "BTC", "long", "market", "trade")
2315 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order
))
2317 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2318 D("0.1"), "BTC", "long", "market", "trade",
2319 close_if_possible
=True)
2320 self
.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order
))
2322 def test_as_json(self
):
2323 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2324 D("0.1"), "BTC", "long", "market", "trade")
2325 mouvement_mock1
= mock
.Mock()
2326 mouvement_mock1
.as_json
.return_value
= 1
2327 mouvement_mock2
= mock
.Mock()
2328 mouvement_mock2
.as_json
.return_value
= 2
2330 order
.mouvements
= [mouvement_mock1
, mouvement_mock2
]
2331 as_json
= order
.as_json()
2332 self
.assertEqual("buy", as_json
["action"])
2333 self
.assertEqual("long", as_json
["trade_type"])
2334 self
.assertEqual(10, as_json
["amount"])
2335 self
.assertEqual("ETH", as_json
["currency"])
2336 self
.assertEqual("BTC", as_json
["base_currency"])
2337 self
.assertEqual(D("0.1"), as_json
["rate"])
2338 self
.assertEqual("pending", as_json
["status"])
2339 self
.assertEqual(False, as_json
["close_if_possible"])
2340 self
.assertIsNone(as_json
["id"])
2341 self
.assertEqual([1, 2], as_json
["mouvements"])
2343 def test_account(self
):
2344 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2345 D("0.1"), "BTC", "long", "market", "trade")
2346 self
.assertEqual("exchange", order
.account
)
2348 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
2349 D("0.1"), "BTC", "short", "market", "trade")
2350 self
.assertEqual("margin", order
.account
)
2352 def test_pending(self
):
2353 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2354 D("0.1"), "BTC", "long", "market", "trade")
2355 self
.assertTrue(order
.pending
)
2356 order
.status
= "open"
2357 self
.assertFalse(order
.pending
)
2359 def test_open(self
):
2360 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2361 D("0.1"), "BTC", "long", "market", "trade")
2362 self
.assertFalse(order
.open)
2363 order
.status
= "open"
2364 self
.assertTrue(order
.open)
2366 def test_finished(self
):
2367 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2368 D("0.1"), "BTC", "long", "market", "trade")
2369 self
.assertFalse(order
.finished
)
2370 order
.status
= "closed"
2371 self
.assertTrue(order
.finished
)
2372 order
.status
= "canceled"
2373 self
.assertTrue(order
.finished
)
2374 order
.status
= "error"
2375 self
.assertTrue(order
.finished
)
2377 @mock.patch.object(portfolio
.Order
, "fetch")
2378 def test_cancel(self
, fetch
):
2379 with self
.subTest(debug
=True):
2381 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2382 D("0.1"), "BTC", "long", self
.m
, "trade")
2383 order
.status
= "open"
2386 self
.m
.ccxt
.cancel_order
.assert_not_called()
2387 self
.m
.report
.log_debug_action
.assert_called_once()
2388 self
.m
.report
.log_debug_action
.reset_mock()
2389 self
.assertEqual("canceled", order
.status
)
2391 with self
.subTest(desc
="Nominal case"):
2392 self
.m
.debug
= False
2393 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2394 D("0.1"), "BTC", "long", self
.m
, "trade")
2395 order
.status
= "open"
2399 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
2400 fetch
.assert_called_once_with()
2401 self
.m
.report
.log_debug_action
.assert_not_called()
2403 with self
.subTest(exception
=True):
2404 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
2405 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2406 D("0.1"), "BTC", "long", self
.m
, "trade")
2407 order
.status
= "open"
2410 self
.m
.ccxt
.cancel_order
.assert_called_with(42)
2411 self
.m
.report
.log_error
.assert_called_once()
2414 with self
.subTest(id=None):
2415 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
2416 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2417 D("0.1"), "BTC", "long", self
.m
, "trade")
2418 order
.status
= "open"
2420 self
.m
.ccxt
.cancel_order
.assert_not_called()
2423 with self
.subTest(open=False):
2424 self
.m
.ccxt
.cancel_order
.side_effect
= portfolio
.OrderNotFound
2425 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2426 D("0.1"), "BTC", "long", self
.m
, "trade")
2427 order
.status
= "closed"
2429 self
.m
.ccxt
.cancel_order
.assert_not_called()
2431 def test_dust_amount_remaining(self
):
2432 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2433 D("0.1"), "BTC", "long", self
.m
, "trade")
2434 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", 1))
2435 self
.assertFalse(order
.dust_amount_remaining())
2437 order
.remaining_amount
= mock
.Mock(return_value
=portfolio
.Amount("ETH", D("0.0001")))
2438 self
.assertTrue(order
.dust_amount_remaining())
2440 @mock.patch.object(portfolio
.Order
, "fetch")
2441 @mock.patch.object(portfolio
.Order
, "filled_amount", return_value
=portfolio
.Amount("ETH", 1))
2442 def test_remaining_amount(self
, filled_amount
, fetch
):
2443 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2444 D("0.1"), "BTC", "long", self
.m
, "trade")
2446 self
.assertEqual(9, order
.remaining_amount().value
)
2448 order
.status
= "open"
2449 self
.assertEqual(9, order
.remaining_amount().value
)
2451 @mock.patch.object(portfolio
.Order
, "fetch")
2452 def test_filled_amount(self
, fetch
):
2453 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2454 D("0.1"), "BTC", "long", self
.m
, "trade")
2455 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
2456 "tradeID": 42, "type": "buy", "fee": "0.0015",
2457 "date": "2017-12-30 12:00:12", "rate": "0.1",
2458 "amount": "3", "total": "0.3"
2460 order
.mouvements
.append(portfolio
.Mouvement("ETH", "BTC", {
2461 "tradeID": 43, "type": "buy", "fee": "0.0015",
2462 "date": "2017-12-30 13:00:12", "rate": "0.2",
2463 "amount": "2", "total": "0.4"
2465 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount())
2466 fetch
.assert_not_called()
2467 order
.status
= "open"
2468 self
.assertEqual(portfolio
.Amount("ETH", 5), order
.filled_amount(in_base_currency
=False))
2469 fetch
.assert_called_once()
2470 self
.assertEqual(portfolio
.Amount("BTC", "0.7"), order
.filled_amount(in_base_currency
=True))
2472 def test_fetch_mouvements(self
):
2473 self
.m
.ccxt
.privatePostReturnOrderTrades
.return_value
= [
2475 "tradeID": 42, "type": "buy", "fee": "0.0015",
2476 "date": "2017-12-30 12:00:12", "rate": "0.1",
2477 "amount": "3", "total": "0.3"
2480 "tradeID": 43, "type": "buy", "fee": "0.0015",
2481 "date": "2017-12-30 13:00:12", "rate": "0.2",
2482 "amount": "2", "total": "0.4"
2485 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2486 D("0.1"), "BTC", "long", self
.m
, "trade")
2488 order
.mouvements
= ["Foo", "Bar", "Baz"]
2490 order
.fetch_mouvements()
2492 self
.m
.ccxt
.privatePostReturnOrderTrades
.assert_called_with({"orderNumber": 12}
)
2493 self
.assertEqual(2, len(order
.mouvements
))
2494 self
.assertEqual(42, order
.mouvements
[0].id)
2495 self
.assertEqual(43, order
.mouvements
[1].id)
2497 self
.m
.ccxt
.privatePostReturnOrderTrades
.side_effect
= portfolio
.ExchangeError
2498 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2499 D("0.1"), "BTC", "long", self
.m
, "trade")
2500 order
.fetch_mouvements()
2501 self
.assertEqual(0, len(order
.mouvements
))
2503 def test_mark_finished_order(self
):
2504 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2505 D("0.1"), "BTC", "short", self
.m
, "trade",
2506 close_if_possible
=True)
2507 order
.status
= "closed"
2508 self
.m
.debug
= False
2510 order
.mark_finished_order()
2511 self
.m
.ccxt
.close_margin_position
.assert_called_with("ETH", "BTC")
2512 self
.m
.ccxt
.close_margin_position
.reset_mock()
2514 order
.status
= "open"
2515 order
.mark_finished_order()
2516 self
.m
.ccxt
.close_margin_position
.assert_not_called()
2518 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2519 D("0.1"), "BTC", "short", self
.m
, "trade",
2520 close_if_possible
=False)
2521 order
.status
= "closed"
2522 order
.mark_finished_order()
2523 self
.m
.ccxt
.close_margin_position
.assert_not_called()
2525 order
= portfolio
.Order("sell", portfolio
.Amount("ETH", 10),
2526 D("0.1"), "BTC", "short", self
.m
, "trade",
2527 close_if_possible
=True)
2528 order
.status
= "closed"
2529 order
.mark_finished_order()
2530 self
.m
.ccxt
.close_margin_position
.assert_not_called()
2532 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2533 D("0.1"), "BTC", "long", self
.m
, "trade",
2534 close_if_possible
=True)
2535 order
.status
= "closed"
2536 order
.mark_finished_order()
2537 self
.m
.ccxt
.close_margin_position
.assert_not_called()
2541 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2542 D("0.1"), "BTC", "short", self
.m
, "trade",
2543 close_if_possible
=True)
2544 order
.status
= "closed"
2546 order
.mark_finished_order()
2547 self
.m
.ccxt
.close_margin_position
.assert_not_called()
2548 self
.m
.report
.log_debug_action
.assert_called_once()
2550 @mock.patch.object(portfolio
.Order
, "fetch_mouvements")
2551 def test_fetch(self
, fetch_mouvements
):
2552 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2553 D("0.1"), "BTC", "long", self
.m
, "trade")
2555 with self
.subTest(debug
=True):
2558 self
.m
.report
.log_debug_action
.assert_called_once()
2559 self
.m
.report
.log_debug_action
.reset_mock()
2560 self
.m
.ccxt
.fetch_order
.assert_not_called()
2561 fetch_mouvements
.assert_not_called()
2563 with self
.subTest(debug
=False):
2564 self
.m
.debug
= False
2565 self
.m
.ccxt
.fetch_order
.return_value
= {
2567 "datetime": "timestamp"
2571 self
.m
.ccxt
.fetch_order
.assert_called_once_with(45)
2572 fetch_mouvements
.assert_called_once()
2573 self
.assertEqual("foo", order
.status
)
2574 self
.assertEqual("timestamp", order
.timestamp
)
2575 self
.assertEqual(1, len(order
.results
))
2576 self
.m
.report
.log_debug_action
.assert_not_called()
2578 with self
.subTest(missing_order
=True):
2579 self
.m
.ccxt
.fetch_order
.side_effect
= [
2580 portfolio
.OrderNotCached
,
2583 self
.assertEqual("closed_unknown", order
.status
)
2585 @mock.patch.object(portfolio
.Order
, "fetch")
2586 @mock.patch.object(portfolio
.Order
, "mark_finished_order")
2587 def test_get_status(self
, mark_finished_order
, fetch
):
2588 with self
.subTest(debug
=True):
2590 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2591 D("0.1"), "BTC", "long", self
.m
, "trade")
2592 self
.assertEqual("pending", order
.get_status())
2593 fetch
.assert_not_called()
2594 self
.m
.report
.log_debug_action
.assert_called_once()
2596 with self
.subTest(debug
=False, finished
=False):
2597 self
.m
.debug
= False
2598 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2599 D("0.1"), "BTC", "long", self
.m
, "trade")
2601 def update_status():
2602 order
.status
= "open"
2603 return update_status
2604 fetch
.side_effect
= _fetch(order
)
2605 self
.assertEqual("open", order
.get_status())
2606 mark_finished_order
.assert_not_called()
2607 fetch
.assert_called_once()
2609 mark_finished_order
.reset_mock()
2611 with self
.subTest(debug
=False, finished
=True):
2612 self
.m
.debug
= False
2613 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2614 D("0.1"), "BTC", "long", self
.m
, "trade")
2616 def update_status():
2617 order
.status
= "closed"
2618 return update_status
2619 fetch
.side_effect
= _fetch(order
)
2620 self
.assertEqual("closed", order
.get_status())
2621 mark_finished_order
.assert_called_once()
2622 fetch
.assert_called_once()
2625 self
.m
.ccxt
.order_precision
.return_value
= 4
2626 with self
.subTest(debug
=True):
2628 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2629 D("0.1"), "BTC", "long", self
.m
, "trade")
2631 self
.m
.ccxt
.create_order
.assert_not_called()
2632 self
.m
.report
.log_debug_action
.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
2633 self
.assertEqual("open", order
.status
)
2634 self
.assertEqual(1, len(order
.results
))
2635 self
.assertEqual(-1, order
.id)
2637 self
.m
.ccxt
.create_order
.reset_mock()
2638 with self
.subTest(debug
=False):
2639 self
.m
.debug
= False
2640 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2641 D("0.1"), "BTC", "long", self
.m
, "trade")
2642 self
.m
.ccxt
.create_order
.return_value
= { "id": 123 }
2644 self
.m
.ccxt
.create_order
.assert_called_once()
2645 self
.assertEqual(1, len(order
.results
))
2646 self
.assertEqual("open", order
.status
)
2648 self
.m
.ccxt
.create_order
.reset_mock()
2649 with self
.subTest(exception
=True):
2650 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 10),
2651 D("0.1"), "BTC", "long", self
.m
, "trade")
2652 self
.m
.ccxt
.create_order
.side_effect
= Exception("bouh")
2654 self
.m
.ccxt
.create_order
.assert_called_once()
2655 self
.assertEqual(0, len(order
.results
))
2656 self
.assertEqual("error", order
.status
)
2657 self
.m
.report
.log_error
.assert_called_once()
2659 self
.m
.ccxt
.create_order
.reset_mock()
2660 with self
.subTest(dust_amount_exception
=True),\
2661 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
2662 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", 0.001),
2663 D("0.1"), "BTC", "long", self
.m
, "trade")
2664 self
.m
.ccxt
.create_order
.side_effect
= portfolio
.InvalidOrder
2666 self
.m
.ccxt
.create_order
.assert_called_once()
2667 self
.assertEqual(0, len(order
.results
))
2668 self
.assertEqual("closed", order
.status
)
2669 mark_finished_order
.assert_called_once()
2671 self
.m
.ccxt
.order_precision
.return_value
= 8
2672 self
.m
.ccxt
.create_order
.reset_mock()
2673 with self
.subTest(insufficient_funds
=True),\
2674 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
2675 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
2676 D("0.1"), "BTC", "long", self
.m
, "trade")
2677 self
.m
.ccxt
.create_order
.side_effect
= [
2678 portfolio
.InsufficientFunds
,
2679 portfolio
.InsufficientFunds
,
2680 portfolio
.InsufficientFunds
,
2684 self
.m
.ccxt
.create_order
.assert_has_calls([
2685 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
2686 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
2687 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
2688 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
2690 self
.assertEqual(4, self
.m
.ccxt
.create_order
.call_count
)
2691 self
.assertEqual(1, len(order
.results
))
2692 self
.assertEqual("open", order
.status
)
2693 self
.assertEqual(4, order
.tries
)
2694 self
.m
.report
.log_error
.assert_called()
2695 self
.assertEqual(4, self
.m
.report
.log_error
.call_count
)
2697 self
.m
.ccxt
.order_precision
.return_value
= 8
2698 self
.m
.ccxt
.create_order
.reset_mock()
2699 self
.m
.report
.log_error
.reset_mock()
2700 with self
.subTest(insufficient_funds
=True),\
2701 mock
.patch
.object(portfolio
.Order
, "mark_finished_order") as mark_finished_order
:
2702 order
= portfolio
.Order("buy", portfolio
.Amount("ETH", "0.001"),
2703 D("0.1"), "BTC", "long", self
.m
, "trade")
2704 self
.m
.ccxt
.create_order
.side_effect
= [
2705 portfolio
.InsufficientFunds
,
2706 portfolio
.InsufficientFunds
,
2707 portfolio
.InsufficientFunds
,
2708 portfolio
.InsufficientFunds
,
2709 portfolio
.InsufficientFunds
,
2712 self
.m
.ccxt
.create_order
.assert_has_calls([
2713 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account
='exchange', price
=D('0.1')),
2714 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account
='exchange', price
=D('0.1')),
2715 mock
.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account
='exchange', price
=D('0.1')),
2716 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account
='exchange', price
=D('0.1')),
2717 mock
.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account
='exchange', price
=D('0.1')),
2719 self
.assertEqual(5, self
.m
.ccxt
.create_order
.call_count
)
2720 self
.assertEqual(0, len(order
.results
))
2721 self
.assertEqual("error", order
.status
)
2722 self
.assertEqual(5, order
.tries
)
2723 self
.m
.report
.log_error
.assert_called()
2724 self
.assertEqual(5, self
.m
.report
.log_error
.call_count
)
2725 self
.m
.report
.log_error
.assert_called_with(mock
.ANY
, message
="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception
=mock
.ANY
)
2728 @unittest.skipUnless("unit" in limits
, "Unit skipped")
2729 class MouvementTest(WebMockTestCase
):
2730 def test_values(self
):
2731 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2732 "tradeID": 42, "type": "buy", "fee": "0.0015",
2733 "date": "2017-12-30 12:00:12", "rate": "0.1",
2734 "amount": "10", "total": "1"
2736 self
.assertEqual("ETH", mouvement
.currency
)
2737 self
.assertEqual("BTC", mouvement
.base_currency
)
2738 self
.assertEqual(42, mouvement
.id)
2739 self
.assertEqual("buy", mouvement
.action
)
2740 self
.assertEqual(D("0.0015"), mouvement
.fee_rate
)
2741 self
.assertEqual(portfolio
.datetime(2017, 12, 30, 12, 0, 12), mouvement
.date
)
2742 self
.assertEqual(D("0.1"), mouvement
.rate
)
2743 self
.assertEqual(portfolio
.Amount("ETH", "10"), mouvement
.total
)
2744 self
.assertEqual(portfolio
.Amount("BTC", "1"), mouvement
.total_in_base
)
2746 mouvement
= portfolio
.Mouvement("ETH", "BTC", { "foo": "bar" }
)
2747 self
.assertIsNone(mouvement
.date
)
2748 self
.assertIsNone(mouvement
.id)
2749 self
.assertIsNone(mouvement
.action
)
2750 self
.assertEqual(-1, mouvement
.fee_rate
)
2751 self
.assertEqual(0, mouvement
.rate
)
2752 self
.assertEqual(portfolio
.Amount("ETH", 0), mouvement
.total
)
2753 self
.assertEqual(portfolio
.Amount("BTC", 0), mouvement
.total_in_base
)
2755 def test__repr(self
):
2756 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2757 "tradeID": 42, "type": "buy", "fee": "0.0015",
2758 "date": "2017-12-30 12:00:12", "rate": "0.1",
2759 "amount": "10", "total": "1"
2761 self
.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement
))
2763 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2764 "tradeID": 42, "type": "buy",
2765 "date": "garbage", "rate": "0.1",
2766 "amount": "10", "total": "1"
2768 self
.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement
))
2770 def test_as_json(self
):
2771 mouvement
= portfolio
.Mouvement("ETH", "BTC", {
2772 "tradeID": 42, "type": "buy", "fee": "0.0015",
2773 "date": "2017-12-30 12:00:12", "rate": "0.1",
2774 "amount": "10", "total": "1"
2776 as_json
= mouvement
.as_json()
2778 self
.assertEqual(D("0.0015"), as_json
["fee_rate"])
2779 self
.assertEqual(portfolio
.datetime(2017, 12, 30, 12, 0, 12), as_json
["date"])
2780 self
.assertEqual("buy", as_json
["action"])
2781 self
.assertEqual(D("10"), as_json
["total"])
2782 self
.assertEqual(D("1"), as_json
["total_in_base"])
2783 self
.assertEqual("BTC", as_json
["base_currency"])
2784 self
.assertEqual("ETH", as_json
["currency"])
2786 @unittest.skipUnless("unit" in limits
, "Unit skipped")
2787 class ReportStoreTest(WebMockTestCase
):
2788 def test_add_log(self
):
2789 report_store
= market
.ReportStore(self
.m
)
2790 report_store
.add_log({"foo": "bar"}
)
2792 self
.assertEqual({"foo": "bar", "date": mock.ANY}
, report_store
.logs
[0])
2794 def test_set_verbose(self
):
2795 report_store
= market
.ReportStore(self
.m
)
2796 with self
.subTest(verbose
=True):
2797 report_store
.set_verbose(True)
2798 self
.assertTrue(report_store
.verbose_print
)
2800 with self
.subTest(verbose
=False):
2801 report_store
.set_verbose(False)
2802 self
.assertFalse(report_store
.verbose_print
)
2804 def test_print_log(self
):
2805 report_store
= market
.ReportStore(self
.m
)
2806 with self
.subTest(verbose
=True),\
2807 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
2808 report_store
.set_verbose(True)
2809 report_store
.print_log("Coucou")
2810 report_store
.print_log(portfolio
.Amount("BTC", 1))
2811 self
.assertEqual(stdout_mock
.getvalue(), "Coucou\n1.00000000 BTC\n")
2813 with self
.subTest(verbose
=False),\
2814 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
2815 report_store
.set_verbose(False)
2816 report_store
.print_log("Coucou")
2817 report_store
.print_log(portfolio
.Amount("BTC", 1))
2818 self
.assertEqual(stdout_mock
.getvalue(), "")
2820 def test_to_json(self
):
2821 report_store
= market
.ReportStore(self
.m
)
2822 report_store
.logs
.append({"foo": "bar"}
)
2823 self
.assertEqual('[\n {\n "foo": "bar"\n }\n]', report_store
.to_json())
2824 report_store
.logs
.append({"date": portfolio.datetime(2018, 2, 24)}
)
2825 self
.assertEqual('[\n {\n "foo": "bar"\n },\n {\n "date": "2018-02-24T00:00:00"\n }\n]', report_store
.to_json())
2826 report_store
.logs
.append({"amount": portfolio.Amount("BTC", 1)}
)
2827 self
.assertEqual('[\n {\n "foo": "bar"\n },\n {\n "date": "2018-02-24T00:00:00"\n },\n {\n "amount": "1.00000000 BTC"\n }\n]', report_store
.to_json())
2829 @mock.patch.object(market
.ReportStore
, "print_log")
2830 @mock.patch.object(market
.ReportStore
, "add_log")
2831 def test_log_stage(self
, add_log
, print_log
):
2832 report_store
= market
.ReportStore(self
.m
)
2834 report_store
.log_stage("foo", bar
="baz", c
=c
, d
=portfolio
.Amount("BTC", 1))
2835 print_log
.assert_has_calls([
2836 mock
.call("-----------"),
2837 mock
.call("[Stage] foo bar=baz, c=c = lambda x: x, d={'currency': 'BTC', 'value': Decimal('1')}"),
2839 add_log
.assert_called_once_with({
2844 'c': 'c = lambda x: x',
2852 @mock.patch.object(market
.ReportStore
, "print_log")
2853 @mock.patch.object(market
.ReportStore
, "add_log")
2854 def test_log_balances(self
, add_log
, print_log
):
2855 report_store
= market
.ReportStore(self
.m
)
2856 self
.m
.balances
.as_json
.return_value
= "json"
2857 self
.m
.balances
.all
= { "FOO": "bar", "BAR": "baz" }
2859 report_store
.log_balances(tag
="tag")
2860 print_log
.assert_has_calls([
2861 mock
.call("[Balance]"),
2865 add_log
.assert_called_once_with({
2871 @mock.patch.object(market
.ReportStore
, "print_log")
2872 @mock.patch.object(market
.ReportStore
, "add_log")
2873 def test_log_tickers(self
, add_log
, print_log
):
2874 report_store
= market
.ReportStore(self
.m
)
2876 "BTC": portfolio
.Amount("BTC", 10),
2877 "ETH": portfolio
.Amount("BTC", D("0.3"))
2879 amounts
["ETH"].rate
= D("0.1")
2881 report_store
.log_tickers(amounts
, "BTC", "default", "total")
2882 print_log
.assert_not_called()
2883 add_log
.assert_called_once_with({
2885 'compute_value': 'default',
2886 'balance_type': 'total',
2899 add_log
.reset_mock()
2900 compute_value
= lambda x
: x
["bid"]
2901 report_store
.log_tickers(amounts
, "BTC", compute_value
, "total")
2902 add_log
.assert_called_once_with({
2904 'compute_value': 'compute_value = lambda x: x["bid"]',
2905 'balance_type': 'total',
2918 @mock.patch.object(market
.ReportStore
, "print_log")
2919 @mock.patch.object(market
.ReportStore
, "add_log")
2920 def test_log_dispatch(self
, add_log
, print_log
):
2921 report_store
= market
.ReportStore(self
.m
)
2922 amount
= portfolio
.Amount("BTC", "10.3")
2924 "BTC": portfolio
.Amount("BTC", 10),
2925 "ETH": portfolio
.Amount("BTC", D("0.3"))
2927 report_store
.log_dispatch(amount
, amounts
, "medium", "repartition")
2928 print_log
.assert_not_called()
2929 add_log
.assert_called_once_with({
2931 'liquidity': 'medium',
2932 'repartition_ratio': 'repartition',
2943 @mock.patch.object(market
.ReportStore
, "print_log")
2944 @mock.patch.object(market
.ReportStore
, "add_log")
2945 def test_log_trades(self
, add_log
, print_log
):
2946 report_store
= market
.ReportStore(self
.m
)
2947 trade_mock1
= mock
.Mock()
2948 trade_mock2
= mock
.Mock()
2949 trade_mock1
.as_json
.return_value
= { "trade": "1" }
2950 trade_mock2
.as_json
.return_value
= { "trade": "2" }
2952 matching_and_trades
= [
2953 (True, trade_mock1
),
2954 (False, trade_mock2
),
2956 report_store
.log_trades(matching_and_trades
, "only")
2958 print_log
.assert_not_called()
2959 add_log
.assert_called_with({
2964 {'trade': '1', 'skipped': False}
,
2965 {'trade': '2', 'skipped': True}
2969 @mock.patch.object(market
.ReportStore
, "print_log")
2970 @mock.patch.object(market
.ReportStore
, "add_log")
2971 def test_log_orders(self
, add_log
, print_log
):
2972 report_store
= market
.ReportStore(self
.m
)
2974 order_mock1
= mock
.Mock()
2975 order_mock2
= mock
.Mock()
2977 order_mock1
.as_json
.return_value
= "order1"
2978 order_mock2
.as_json
.return_value
= "order2"
2980 orders
= [order_mock1
, order_mock2
]
2982 report_store
.log_orders(orders
, tick
="tick",
2983 only
="only", compute_value
="compute_value")
2985 print_log
.assert_called_once_with("[Orders]")
2986 self
.m
.trades
.print_all_with_order
.assert_called_once_with(ind
="\t")
2988 add_log
.assert_called_with({
2991 'compute_value': 'compute_value',
2993 'orders': ['order1', 'order2']
2996 add_log
.reset_mock()
2997 def compute_value(x
, y
):
2999 report_store
.log_orders(orders
, tick
="tick",
3000 only
="only", compute_value
=compute_value
)
3001 add_log
.assert_called_with({
3004 'compute_value': 'def compute_value(x, y):\n return x[y]',
3006 'orders': ['order1', 'order2']
3010 @mock.patch.object(market
.ReportStore
, "print_log")
3011 @mock.patch.object(market
.ReportStore
, "add_log")
3012 def test_log_order(self
, add_log
, print_log
):
3013 report_store
= market
.ReportStore(self
.m
)
3014 order_mock
= mock
.Mock()
3015 order_mock
.as_json
.return_value
= "order"
3016 new_order_mock
= mock
.Mock()
3017 new_order_mock
.as_json
.return_value
= "new_order"
3018 order_mock
.__repr
__ = mock
.Mock()
3019 order_mock
.__repr
__.return_value
= "Order Mock"
3020 new_order_mock
.__repr
__ = mock
.Mock()
3021 new_order_mock
.__repr
__.return_value
= "New order Mock"
3023 with self
.subTest(finished
=True):
3024 report_store
.log_order(order_mock
, 1, finished
=True)
3025 print_log
.assert_called_once_with("[Order] Finished Order Mock")
3026 add_log
.assert_called_once_with({
3031 'compute_value': None,
3035 add_log
.reset_mock()
3036 print_log
.reset_mock()
3038 with self
.subTest(update
="waiting"):
3039 report_store
.log_order(order_mock
, 1, update
="waiting")
3040 print_log
.assert_called_once_with("[Order] Order Mock, tick 1, waiting")
3041 add_log
.assert_called_once_with({
3044 'update': 'waiting',
3046 'compute_value': None,
3050 add_log
.reset_mock()
3051 print_log
.reset_mock()
3052 with self
.subTest(update
="adjusting"):
3053 compute_value
= lambda x
: (x
["bid"] + x
["ask"]*2)/3
3054 report_store
.log_order(order_mock
, 3,
3055 update
="adjusting", new_order
=new_order_mock
,
3056 compute_value
=compute_value
)
3057 print_log
.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
3058 add_log
.assert_called_once_with({
3061 'update': 'adjusting',
3063 'compute_value': 'compute_value = lambda x: (x["bid"] + x["ask"]*2)/3',
3064 'new_order': 'new_order'
3067 add_log
.reset_mock()
3068 print_log
.reset_mock()
3069 with self
.subTest(update
="market_fallback"):
3070 report_store
.log_order(order_mock
, 7,
3071 update
="market_fallback", new_order
=new_order_mock
)
3072 print_log
.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value")
3073 add_log
.assert_called_once_with({
3076 'update': 'market_fallback',
3078 'compute_value': None,
3079 'new_order': 'new_order'
3082 add_log
.reset_mock()
3083 print_log
.reset_mock()
3084 with self
.subTest(update
="market_adjusting"):
3085 report_store
.log_order(order_mock
, 17,
3086 update
="market_adjust", new_order
=new_order_mock
)
3087 print_log
.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock")
3088 add_log
.assert_called_once_with({
3091 'update': 'market_adjust',
3093 'compute_value': None,
3094 'new_order': 'new_order'
3097 @mock.patch.object(market
.ReportStore
, "print_log")
3098 @mock.patch.object(market
.ReportStore
, "add_log")
3099 def test_log_move_balances(self
, add_log
, print_log
):
3100 report_store
= market
.ReportStore(self
.m
)
3102 "BTC": portfolio
.Amount("BTC", 10),
3106 "BTC": portfolio
.Amount("BTC", 3),
3109 report_store
.log_move_balances(needed
, moving
)
3110 print_log
.assert_not_called()
3111 add_log
.assert_called_once_with({
3112 'type': 'move_balances',
3124 @mock.patch.object(market
.ReportStore
, "print_log")
3125 @mock.patch.object(market
.ReportStore
, "add_log")
3126 def test_log_http_request(self
, add_log
, print_log
):
3127 report_store
= market
.ReportStore(self
.m
)
3128 response
= mock
.Mock()
3129 response
.status_code
= 200
3130 response
.text
= "Hey"
3132 report_store
.log_http_request("method", "url", "body",
3133 "headers", response
)
3134 print_log
.assert_not_called()
3135 add_log
.assert_called_once_with({
3136 'type': 'http_request',
3140 'headers': 'headers',
3145 @mock.patch.object(market
.ReportStore
, "print_log")
3146 @mock.patch.object(market
.ReportStore
, "add_log")
3147 def test_log_error(self
, add_log
, print_log
):
3148 report_store
= market
.ReportStore(self
.m
)
3149 with self
.subTest(message
=None, exception
=None):
3150 report_store
.log_error("action")
3151 print_log
.assert_called_once_with("[Error] action")
3152 add_log
.assert_called_once_with({
3155 'exception_class': None,
3156 'exception_message': None,
3160 print_log
.reset_mock()
3161 add_log
.reset_mock()
3162 with self
.subTest(message
="Hey", exception
=None):
3163 report_store
.log_error("action", message
="Hey")
3164 print_log
.assert_has_calls([
3165 mock
.call("[Error] action"),
3168 add_log
.assert_called_once_with({
3171 'exception_class': None,
3172 'exception_message': None,
3176 print_log
.reset_mock()
3177 add_log
.reset_mock()
3178 with self
.subTest(message
=None, exception
=Exception("bouh")):
3179 report_store
.log_error("action", exception
=Exception("bouh"))
3180 print_log
.assert_has_calls([
3181 mock
.call("[Error] action"),
3182 mock
.call("\tException: bouh")
3184 add_log
.assert_called_once_with({
3187 'exception_class': "Exception",
3188 'exception_message': "bouh",
3192 print_log
.reset_mock()
3193 add_log
.reset_mock()
3194 with self
.subTest(message
="Hey", exception
=Exception("bouh")):
3195 report_store
.log_error("action", message
="Hey", exception
=Exception("bouh"))
3196 print_log
.assert_has_calls([
3197 mock
.call("[Error] action"),
3198 mock
.call("\tException: bouh"),
3201 add_log
.assert_called_once_with({
3204 'exception_class': "Exception",
3205 'exception_message': "bouh",
3209 @mock.patch.object(market
.ReportStore
, "print_log")
3210 @mock.patch.object(market
.ReportStore
, "add_log")
3211 def test_log_debug_action(self
, add_log
, print_log
):
3212 report_store
= market
.ReportStore(self
.m
)
3213 report_store
.log_debug_action("Hey")
3215 print_log
.assert_called_once_with("[Debug] Hey")
3216 add_log
.assert_called_once_with({
3217 'type': 'debug_action',
3221 @unittest.skipUnless("unit" in limits
, "Unit skipped")
3222 class MainTest(WebMockTestCase
):
3223 def test_make_order(self
):
3224 self
.m
.get_ticker
.return_value
= {
3226 "average": D("0.1"),
3231 with self
.subTest(description
="nominal case"):
3232 main
.make_order(self
.m
, 10, "ETH")
3234 self
.m
.report
.log_stage
.assert_has_calls([
3235 mock
.call("make_order_begin"),
3236 mock
.call("make_order_end"),
3238 self
.m
.balances
.fetch_balances
.assert_has_calls([
3239 mock
.call(tag
="make_order_begin"),
3240 mock
.call(tag
="make_order_end"),
3242 self
.m
.trades
.all
.append
.assert_called_once()
3243 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3244 self
.assertEqual(False, trade
.orders
[0].close_if_possible
)
3245 self
.assertEqual(0, trade
.value_from
)
3246 self
.assertEqual("ETH", trade
.currency
)
3247 self
.assertEqual("BTC", trade
.base_currency
)
3248 self
.m
.report
.log_orders
.assert_called_once_with([trade
.orders
[0]], None, "average")
3249 self
.m
.trades
.run_orders
.assert_called_once_with()
3250 self
.m
.follow_orders
.assert_called_once_with()
3252 order
= trade
.orders
[0]
3253 self
.assertEqual(D("0.10"), order
.rate
)
3256 with self
.subTest(compute_value
="default"):
3257 main
.make_order(self
.m
, 10, "ETH", action
="dispose",
3258 compute_value
="ask")
3260 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3261 order
= trade
.orders
[0]
3262 self
.assertEqual(D("0.11"), order
.rate
)
3265 with self
.subTest(follow
=False):
3266 result
= main
.make_order(self
.m
, 10, "ETH", follow
=False)
3268 self
.m
.report
.log_stage
.assert_has_calls([
3269 mock
.call("make_order_begin"),
3270 mock
.call("make_order_end_not_followed"),
3272 self
.m
.balances
.fetch_balances
.assert_called_once_with(tag
="make_order_begin")
3274 self
.m
.trades
.all
.append
.assert_called_once()
3275 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3276 self
.assertEqual(0, trade
.value_from
)
3277 self
.assertEqual("ETH", trade
.currency
)
3278 self
.assertEqual("BTC", trade
.base_currency
)
3279 self
.m
.report
.log_orders
.assert_called_once_with([trade
.orders
[0]], None, "average")
3280 self
.m
.trades
.run_orders
.assert_called_once_with()
3281 self
.m
.follow_orders
.assert_not_called()
3282 self
.assertEqual(trade
.orders
[0], result
)
3285 with self
.subTest(base_currency
="USDT"):
3286 main
.make_order(self
.m
, 1, "BTC", base_currency
="USDT")
3288 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3289 self
.assertEqual("BTC", trade
.currency
)
3290 self
.assertEqual("USDT", trade
.base_currency
)
3293 with self
.subTest(close_if_possible
=True):
3294 main
.make_order(self
.m
, 10, "ETH", close_if_possible
=True)
3296 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3297 self
.assertEqual(True, trade
.orders
[0].close_if_possible
)
3300 with self
.subTest(action
="dispose"):
3301 main
.make_order(self
.m
, 10, "ETH", action
="dispose")
3303 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3304 self
.assertEqual(0, trade
.value_to
)
3305 self
.assertEqual(1, trade
.value_from
.value
)
3306 self
.assertEqual("ETH", trade
.currency
)
3307 self
.assertEqual("BTC", trade
.base_currency
)
3310 with self
.subTest(compute_value
="default"):
3311 main
.make_order(self
.m
, 10, "ETH", action
="dispose",
3312 compute_value
="bid")
3314 trade
= self
.m
.trades
.all
.append
.mock_calls
[0][1][0]
3315 self
.assertEqual(D("0.9"), trade
.value_from
.value
)
3317 def test_get_user_market(self
):
3318 with mock
.patch("main.fetch_markets") as main_fetch_markets
,\
3319 mock
.patch("main.parse_config") as main_parse_config
:
3320 with self
.subTest(debug
=False):
3321 main_parse_config
.return_value
= ["pg_config", "report_path"]
3322 main_fetch_markets
.return_value
= [({"key": "market_config"}
,)]
3323 m
= main
.get_user_market("config_path.ini", 1)
3325 self
.assertIsInstance(m
, market
.Market
)
3326 self
.assertFalse(m
.debug
)
3328 with self
.subTest(debug
=True):
3329 main_parse_config
.return_value
= ["pg_config", "report_path"]
3330 main_fetch_markets
.return_value
= [({"key": "market_config"}
,)]
3331 m
= main
.get_user_market("config_path.ini", 1, debug
=True)
3333 self
.assertIsInstance(m
, market
.Market
)
3334 self
.assertTrue(m
.debug
)
3336 def test_process(self
):
3337 with mock
.patch("market.Market") as market_mock
,\
3338 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
3340 args_mock
= mock
.Mock()
3341 args_mock
.action
= "action"
3342 args_mock
.config
= "config"
3343 args_mock
.user
= "user"
3344 args_mock
.debug
= "debug"
3345 args_mock
.before
= "before"
3346 args_mock
.after
= "after"
3347 self
.assertEqual("", stdout_mock
.getvalue())
3349 main
.process("config", 1, "report_path", args_mock
)
3351 market_mock
.from_config
.assert_has_calls([
3352 mock
.call("config", debug
="debug", user_id
=1, report_path
="report_path"),
3353 mock
.call().process("action", before
="before", after
="after"),
3356 with self
.subTest(exception
=True):
3357 market_mock
.from_config
.side_effect
= Exception("boo")
3358 main
.process("config", 1, "report_path", args_mock
)
3359 self
.assertEqual("Exception: boo\n", stdout_mock
.getvalue())
3361 def test_main(self
):
3362 with mock
.patch("main.parse_args") as parse_args
,\
3363 mock
.patch("main.parse_config") as parse_config
,\
3364 mock
.patch("main.fetch_markets") as fetch_markets
,\
3365 mock
.patch("main.process") as process
:
3367 args_mock
= mock
.Mock()
3368 args_mock
.config
= "config"
3369 args_mock
.user
= "user"
3370 parse_args
.return_value
= args_mock
3372 parse_config
.return_value
= ["pg_config", "report_path"]
3374 fetch_markets
.return_value
= [["config1", 1], ["config2", 2]]
3376 main
.main(["Foo", "Bar"])
3378 parse_args
.assert_called_with(["Foo", "Bar"])
3379 parse_config
.assert_called_with("config")
3380 fetch_markets
.assert_called_with("pg_config", "user")
3382 self
.assertEqual(2, process
.call_count
)
3383 process
.assert_has_calls([
3384 mock
.call("config1", 1, "report_path", args_mock
),
3385 mock
.call("config2", 2, "report_path", args_mock
),
3388 @mock.patch.object(main
.sys
, "exit")
3389 @mock.patch("main.configparser")
3390 @mock.patch("main.os")
3391 def test_parse_config(self
, os
, configparser
, exit
):
3392 with self
.subTest(pg_config
=True, report_path
=None):
3393 config_mock
= mock
.MagicMock()
3394 configparser
.ConfigParser
.return_value
= config_mock
3395 def config(element
):
3396 return element
== "postgresql"
3398 config_mock
.__contains
__.side_effect
= config
3399 config_mock
.__getitem
__.return_value
= "pg_config"
3401 result
= main
.parse_config("configfile")
3403 config_mock
.read
.assert_called_with("configfile")
3405 self
.assertEqual(["pg_config", None], result
)
3407 with self
.subTest(pg_config
=True, report_path
="present"):
3408 config_mock
= mock
.MagicMock()
3409 configparser
.ConfigParser
.return_value
= config_mock
3411 config_mock
.__contains
__.return_value
= True
3412 config_mock
.__getitem
__.side_effect
= [
3413 {"report_path": "report_path"}
,
3414 {"report_path": "report_path"}
,
3418 os
.path
.exists
.return_value
= False
3419 result
= main
.parse_config("configfile")
3421 config_mock
.read
.assert_called_with("configfile")
3422 self
.assertEqual(["pg_config", "report_path"], result
)
3423 os
.path
.exists
.assert_called_once_with("report_path")
3424 os
.makedirs
.assert_called_once_with("report_path")
3426 with self
.subTest(pg_config
=False),\
3427 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
3428 config_mock
= mock
.MagicMock()
3429 configparser
.ConfigParser
.return_value
= config_mock
3430 result
= main
.parse_config("configfile")
3432 config_mock
.read
.assert_called_with("configfile")
3433 exit
.assert_called_once_with(1)
3434 self
.assertEqual("no configuration for postgresql in config file\n", stdout_mock
.getvalue())
3436 @mock.patch.object(main
.sys
, "exit")
3437 def test_parse_args(self
, exit
):
3438 with self
.subTest(config
="config.ini"):
3439 args
= main
.parse_args([])
3440 self
.assertEqual("config.ini", args
.config
)
3441 self
.assertFalse(args
.before
)
3442 self
.assertFalse(args
.after
)
3443 self
.assertFalse(args
.debug
)
3445 args
= main
.parse_args(["--before", "--after", "--debug"])
3446 self
.assertTrue(args
.before
)
3447 self
.assertTrue(args
.after
)
3448 self
.assertTrue(args
.debug
)
3450 exit
.assert_not_called()
3452 with self
.subTest(config
="inexistant"),\
3453 mock
.patch('sys.stdout', new_callable
=StringIO
) as stdout_mock
:
3454 args
= main
.parse_args(["--config", "foo.bar"])
3455 exit
.assert_called_once_with(1)
3456 self
.assertEqual("no config file found, exiting\n", stdout_mock
.getvalue())
3458 @mock.patch.object(main
, "psycopg2")
3459 def test_fetch_markets(self
, psycopg2
):
3460 connect_mock
= mock
.Mock()
3461 cursor_mock
= mock
.MagicMock()
3462 cursor_mock
.__iter
__.return_value
= ["row_1", "row_2"]
3464 connect_mock
.cursor
.return_value
= cursor_mock
3465 psycopg2
.connect
.return_value
= connect_mock
3467 with self
.subTest(user
=None):
3468 rows
= list(main
.fetch_markets({"foo": "bar"}
, None))
3470 psycopg2
.connect
.assert_called_once_with(foo
="bar")
3471 cursor_mock
.execute
.assert_called_once_with("SELECT config,user_id FROM market_configs")
3473 self
.assertEqual(["row_1", "row_2"], rows
)
3475 psycopg2
.connect
.reset_mock()
3476 cursor_mock
.execute
.reset_mock()
3477 with self
.subTest(user
=1):
3478 rows
= list(main
.fetch_markets({"foo": "bar"}
, 1))
3480 psycopg2
.connect
.assert_called_once_with(foo
="bar")
3481 cursor_mock
.execute
.assert_called_once_with("SELECT config,user_id FROM market_configs WHERE user_id = %s", 1)
3483 self
.assertEqual(["row_1", "row_2"], rows
)
3486 @unittest.skipUnless("unit" in limits
, "Unit skipped")
3487 class ProcessorTest(WebMockTestCase
):
3488 def test_values(self
):
3489 processor
= market
.Processor(self
.m
)
3491 self
.assertEqual(self
.m
, processor
.market
)
3493 def test_run_action(self
):
3494 processor
= market
.Processor(self
.m
)
3496 with mock
.patch
.object(processor
, "parse_args") as parse_args
:
3497 method_mock
= mock
.Mock()
3498 parse_args
.return_value
= [method_mock
, { "foo": "bar" }
]
3500 processor
.run_action("foo", "bar", "baz")
3502 parse_args
.assert_called_with("foo", "bar", "baz")
3504 method_mock
.assert_called_with(foo
="bar")
3506 processor
.run_action("wait_for_recent", "bar", "baz")
3508 method_mock
.assert_called_with(self
.m
, foo
="bar")
3510 def test_select_step(self
):
3511 processor
= market
.Processor(self
.m
)
3513 scenario
= processor
.scenarios
["sell_all"]
3515 self
.assertEqual(scenario
, processor
.select_steps(scenario
, "all"))
3516 self
.assertEqual(["all_sell"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "before"))))
3517 self
.assertEqual(["wait", "all_buy"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "after"))))
3518 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, 2))))
3519 self
.assertEqual(["wait"], list(map(lambda x
: x
["name"], processor
.select_steps(scenario
, "wait"))))
3521 with self
.assertRaises(TypeError):
3522 processor
.select_steps(scenario
, ["wait"])
3524 @mock.patch("market.Processor.process_step")
3525 def test_process(self
, process_step
):
3526 processor
= market
.Processor(self
.m
)
3528 processor
.process("sell_all", foo
="bar")
3529 self
.assertEqual(3, process_step
.call_count
)
3531 steps
= list(map(lambda x
: x
[1][1]["name"], process_step
.mock_calls
))
3532 scenario_names
= list(map(lambda x
: x
[1][0], process_step
.mock_calls
))
3533 kwargs
= list(map(lambda x
: x
[1][2], process_step
.mock_calls
))
3534 self
.assertEqual(["all_sell", "wait", "all_buy"], steps
)
3535 self
.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names
)
3536 self
.assertEqual([{"foo":"bar"}
, {"foo":"bar"}
, {"foo":"bar"}
], kwargs
)
3538 process_step
.reset_mock()
3540 processor
.process("sell_needed", steps
=["before", "after"])
3541 self
.assertEqual(3, process_step
.call_count
)
3543 def test_method_arguments(self
):
3544 ccxt
= mock
.Mock(spec
=market
.ccxt
.poloniexE
)
3545 m
= market
.Market(ccxt
)
3547 processor
= market
.Processor(m
)
3549 method
, arguments
= processor
.method_arguments("wait_for_recent")
3550 self
.assertEqual(portfolio
.Portfolio
.wait_for_recent
, method
)
3551 self
.assertEqual(["delta"], arguments
)
3553 method
, arguments
= processor
.method_arguments("prepare_trades")
3554 self
.assertEqual(m
.prepare_trades
, method
)
3555 self
.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments
)
3557 method
, arguments
= processor
.method_arguments("prepare_orders")
3558 self
.assertEqual(m
.trades
.prepare_orders
, method
)
3560 method
, arguments
= processor
.method_arguments("move_balances")
3561 self
.assertEqual(m
.move_balances
, method
)
3563 method
, arguments
= processor
.method_arguments("run_orders")
3564 self
.assertEqual(m
.trades
.run_orders
, method
)
3566 method
, arguments
= processor
.method_arguments("follow_orders")
3567 self
.assertEqual(m
.follow_orders
, method
)
3569 method
, arguments
= processor
.method_arguments("close_trades")
3570 self
.assertEqual(m
.trades
.close_trades
, method
)
3572 def test_process_step(self
):
3573 processor
= market
.Processor(self
.m
)
3575 with mock
.patch
.object(processor
, "run_action") as run_action
:
3576 step
= processor
.scenarios
["sell_needed"][1]
3578 processor
.process_step("foo", step
, {"foo":"bar"}
)
3580 self
.m
.report
.log_stage
.assert_has_calls([
3581 mock
.call("process_foo__1_sell_begin"),
3582 mock
.call("process_foo__1_sell_end"),
3584 self
.m
.balances
.fetch_balances
.assert_has_calls([
3585 mock
.call(tag
="process_foo__1_sell_begin"),
3586 mock
.call(tag
="process_foo__1_sell_end"),
3589 self
.assertEqual(5, run_action
.call_count
)
3591 run_action
.assert_has_calls([
3592 mock
.call('prepare_trades', {}, {'foo': 'bar'}
),
3593 mock
.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}
, {'foo': 'bar'}
),
3594 mock
.call('run_orders', {}, {'foo': 'bar'}
),
3595 mock
.call('follow_orders', {}, {'foo': 'bar'}
),
3596 mock
.call('close_trades', {}, {'foo': 'bar'}
),
3600 with mock
.patch
.object(processor
, "run_action") as run_action
:
3601 step
= processor
.scenarios
["sell_needed"][0]
3603 processor
.process_step("foo", step
, {"foo":"bar"}
)
3604 self
.m
.balances
.fetch_balances
.assert_not_called()
3606 def test_parse_args(self
):
3607 processor
= market
.Processor(self
.m
)
3609 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
3610 method_mock
= mock
.Mock()
3611 method_arguments
.return_value
= [
3615 method
, args
= processor
.parse_args("action", {"foo": "bar", "foo2": "bar"}
, {"foo": "bar2", "bla": "bla"}
)
3617 self
.assertEqual(method_mock
, method
)
3618 self
.assertEqual({"foo": "bar2", "foo2": "bar"}
, args
)
3620 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
3621 method_mock
= mock
.Mock()
3622 method_arguments
.return_value
= [
3626 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {})
3628 self
.assertEqual(1, len(args
["repartition"]))
3629 self
.assertIn("BTC", args
["repartition"])
3631 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
3632 method_mock
= mock
.Mock()
3633 method_arguments
.return_value
= [
3635 ["repartition", "base_currency"]
3637 method
, args
= processor
.parse_args("action", {"repartition": { "base_currency": 1 }
}, {"base_currency": "USDT"}
)
3639 self
.assertEqual(1, len(args
["repartition"]))
3640 self
.assertIn("USDT", args
["repartition"])
3642 with mock
.patch
.object(processor
, "method_arguments") as method_arguments
:
3643 method_mock
= mock
.Mock()
3644 method_arguments
.return_value
= [
3646 ["repartition", "base_currency"]
3648 method
, args
= processor
.parse_args("action", {"repartition": { "ETH": 1 }
}, {"base_currency": "USDT"}
)
3650 self
.assertEqual(1, len(args
["repartition"]))
3651 self
.assertIn("ETH", args
["repartition"])
3654 @unittest.skipUnless("acceptance" in limits
, "Acceptance skipped")
3655 class AcceptanceTest(WebMockTestCase
):
3656 @unittest.expectedFailure
3657 def test_success_sell_only_necessary(self
):
3658 # FIXME: catch stdout
3659 self
.m
.report
.verbose_print
= False
3662 "exchange_free": D("1.0"),
3663 "exchange_used": D("0.0"),
3664 "exchange_total": D("1.0"),
3668 "exchange_free": D("4.0"),
3669 "exchange_used": D("0.0"),
3670 "exchange_total": D("4.0"),
3674 "exchange_free": D("1000.0"),
3675 "exchange_used": D("0.0"),
3676 "exchange_total": D("1000.0"),
3677 "total": D("1000.0"),
3681 "ETH": (D("0.25"), "long"),
3682 "ETC": (D("0.25"), "long"),
3683 "BTC": (D("0.4"), "long"),
3684 "BTD": (D("0.01"), "short"),
3685 "B2X": (D("0.04"), "long"),
3686 "USDT": (D("0.05"), "long"),
3689 def fetch_ticker(symbol
):
3690 if symbol
== "ETH/BTC":
3692 "symbol": "ETH/BTC",
3696 if symbol
== "ETC/BTC":
3698 "symbol": "ETC/BTC",
3702 if symbol
== "XVG/BTC":
3704 "symbol": "XVG/BTC",
3705 "bid": D("0.00003"),
3708 if symbol
== "BTD/BTC":
3710 "symbol": "BTD/BTC",
3714 if symbol
== "B2X/BTC":
3716 "symbol": "B2X/BTC",
3720 if symbol
== "USDT/BTC":
3721 raise helper
.ExchangeError
3722 if symbol
== "BTC/USDT":
3724 "symbol": "BTC/USDT",
3728 self
.fail("Shouldn't have been called with {}".format(symbol
))
3730 market
= mock
.Mock()
3731 market
.fetch_all_balances
.return_value
= fetch_balance
3732 market
.fetch_ticker
.side_effect
= fetch_ticker
3733 with mock
.patch
.object(portfolio
.Portfolio
, "repartition", return_value
=repartition
):
3735 helper
.prepare_trades(market
)
3737 balances
= portfolio
.BalanceStore
.all
3738 self
.assertEqual(portfolio
.Amount("ETH", 1), balances
["ETH"].total
)
3739 self
.assertEqual(portfolio
.Amount("ETC", 4), balances
["ETC"].total
)
3740 self
.assertEqual(portfolio
.Amount("XVG", 1000), balances
["XVG"].total
)
3743 trades
= portfolio
.TradeStore
.all
3744 self
.assertEqual(portfolio
.Amount("BTC", D("0.15")), trades
[0].value_from
)
3745 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[0].value_to
)
3746 self
.assertEqual("dispose", trades
[0].action
)
3748 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[1].value_from
)
3749 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[1].value_to
)
3750 self
.assertEqual("acquire", trades
[1].action
)
3752 self
.assertEqual(portfolio
.Amount("BTC", D("0.04")), trades
[2].value_from
)
3753 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[2].value_to
)
3754 self
.assertEqual("dispose", trades
[2].action
)
3756 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[3].value_from
)
3757 self
.assertEqual(portfolio
.Amount("BTC", D("-0.002")), trades
[3].value_to
)
3758 self
.assertEqual("acquire", trades
[3].action
)
3760 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[4].value_from
)
3761 self
.assertEqual(portfolio
.Amount("BTC", D("0.008")), trades
[4].value_to
)
3762 self
.assertEqual("acquire", trades
[4].action
)
3764 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[5].value_from
)
3765 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[5].value_to
)
3766 self
.assertEqual("acquire", trades
[5].action
)
3769 portfolio
.TradeStore
.prepare_orders(only
="dispose", compute_value
=lambda x
, y
: x
["bid"] * D("1.001"))
3771 all_orders
= portfolio
.TradeStore
.all_orders(state
="pending")
3772 self
.assertEqual(2, len(all_orders
))
3773 self
.assertEqual(2, 3*all_orders
[0].amount
.value
)
3774 self
.assertEqual(D("0.14014"), all_orders
[0].rate
)
3775 self
.assertEqual(1000, all_orders
[1].amount
.value
)
3776 self
.assertEqual(D("0.00003003"), all_orders
[1].rate
)
3779 def create_order(symbol
, type, action
, amount
, price
=None, account
="exchange"):
3780 self
.assertEqual("limit", type)
3781 if symbol
== "ETH/BTC":
3782 self
.assertEqual("sell", action
)
3783 self
.assertEqual(D('0.66666666'), amount
)
3784 self
.assertEqual(D("0.14014"), price
)
3785 elif symbol
== "XVG/BTC":
3786 self
.assertEqual("sell", action
)
3787 self
.assertEqual(1000, amount
)
3788 self
.assertEqual(D("0.00003003"), price
)
3790 self
.fail("I shouldn't have been called")
3795 market
.create_order
.side_effect
= create_order
3796 market
.order_precision
.return_value
= 8
3799 portfolio
.TradeStore
.run_orders()
3801 self
.assertEqual("open", all_orders
[0].status
)
3802 self
.assertEqual("open", all_orders
[1].status
)
3804 market
.fetch_order
.return_value
= { "status": "closed", "datetime": "2018-01-20 13:40:00" }
3805 market
.privatePostReturnOrderTrades
.return_value
= [
3807 "tradeID": 42, "type": "buy", "fee": "0.0015",
3808 "date": "2017-12-30 12:00:12", "rate": "0.1",
3809 "amount": "10", "total": "1"
3812 with mock
.patch
.object(portfolio
.time
, "sleep") as sleep
:
3814 helper
.follow_orders(verbose
=False)
3816 sleep
.assert_called_with(30)
3818 for order
in all_orders
:
3819 self
.assertEqual("closed", order
.status
)
3823 "exchange_free": D("1.0") / 3,
3824 "exchange_used": D("0.0"),
3825 "exchange_total": D("1.0") / 3,
3827 "total": D("1.0") / 3,
3830 "exchange_free": D("0.134"),
3831 "exchange_used": D("0.0"),
3832 "exchange_total": D("0.134"),
3834 "total": D("0.134"),
3837 "exchange_free": D("4.0"),
3838 "exchange_used": D("0.0"),
3839 "exchange_total": D("4.0"),
3844 "exchange_free": D("0.0"),
3845 "exchange_used": D("0.0"),
3846 "exchange_total": D("0.0"),
3851 market
.fetch_all_balances
.return_value
= fetch_balance
3853 with mock
.patch
.object(portfolio
.Portfolio
, "repartition", return_value
=repartition
):
3855 helper
.prepare_trades(market
, only
="acquire", compute_value
="average")
3857 balances
= portfolio
.BalanceStore
.all
3858 self
.assertEqual(portfolio
.Amount("ETH", 1 / D("3")), balances
["ETH"].total
)
3859 self
.assertEqual(portfolio
.Amount("ETC", 4), balances
["ETC"].total
)
3860 self
.assertEqual(portfolio
.Amount("BTC", D("0.134")), balances
["BTC"].total
)
3861 self
.assertEqual(portfolio
.Amount("XVG", 0), balances
["XVG"].total
)
3864 trades
= portfolio
.TradeStore
.all
3865 self
.assertEqual(portfolio
.Amount("BTC", D("0.15")), trades
[0].value_from
)
3866 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[0].value_to
)
3867 self
.assertEqual("dispose", trades
[0].action
)
3869 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[1].value_from
)
3870 self
.assertEqual(portfolio
.Amount("BTC", D("0.05")), trades
[1].value_to
)
3871 self
.assertEqual("acquire", trades
[1].action
)
3873 self
.assertNotIn("BTC", trades
)
3875 self
.assertEqual(portfolio
.Amount("BTC", D("0.04")), trades
[2].value_from
)
3876 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[2].value_to
)
3877 self
.assertEqual("dispose", trades
[2].action
)
3879 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[3].value_from
)
3880 self
.assertEqual(portfolio
.Amount("BTC", D("-0.002")), trades
[3].value_to
)
3881 self
.assertEqual("acquire", trades
[3].action
)
3883 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[4].value_from
)
3884 self
.assertEqual(portfolio
.Amount("BTC", D("0.008")), trades
[4].value_to
)
3885 self
.assertEqual("acquire", trades
[4].action
)
3887 self
.assertEqual(portfolio
.Amount("BTC", D("0.00")), trades
[5].value_from
)
3888 self
.assertEqual(portfolio
.Amount("BTC", D("0.01")), trades
[5].value_to
)
3889 self
.assertEqual("acquire", trades
[5].action
)
3892 portfolio
.TradeStore
.prepare_orders(only
="acquire", compute_value
=lambda x
, y
: x
["ask"])
3894 all_orders
= portfolio
.TradeStore
.all_orders(state
="pending")
3895 self
.assertEqual(4, len(all_orders
))
3896 self
.assertEqual(portfolio
.Amount("ETC", D("12.83333333")), round(all_orders
[0].amount
))
3897 self
.assertEqual(D("0.003"), all_orders
[0].rate
)
3898 self
.assertEqual("buy", all_orders
[0].action
)
3899 self
.assertEqual("long", all_orders
[0].trade_type
)
3901 self
.assertEqual(portfolio
.Amount("BTD", D("1.61666666")), round(all_orders
[1].amount
))
3902 self
.assertEqual(D("0.0012"), all_orders
[1].rate
)
3903 self
.assertEqual("sell", all_orders
[1].action
)
3904 self
.assertEqual("short", all_orders
[1].trade_type
)
3906 diff
= portfolio
.Amount("B2X", D("19.4")/3) - all_orders
[2].amount
3907 self
.assertAlmostEqual(0, diff
.value
)
3908 self
.assertEqual(D("0.0012"), all_orders
[2].rate
)
3909 self
.assertEqual("buy", all_orders
[2].action
)
3910 self
.assertEqual("long", all_orders
[2].trade_type
)
3912 self
.assertEqual(portfolio
.Amount("BTC", D("0.0097")), all_orders
[3].amount
)
3913 self
.assertEqual(D("16000"), all_orders
[3].rate
)
3914 self
.assertEqual("sell", all_orders
[3].action
)
3915 self
.assertEqual("long", all_orders
[3].trade_type
)
3919 # Move balances to margin
3923 # portfolio.TradeStore.run_orders()
3925 with mock
.patch
.object(portfolio
.time
, "sleep") as sleep
:
3927 helper
.follow_orders(verbose
=False)
3929 sleep
.assert_called_with(30)
3931 if __name__
== '__main__':