aboutsummaryrefslogblamecommitdiff
path: root/test.py
blob: 890381541368f5d683d942584152e0dfbd5324f0 (plain) (tree)
1
2
3
4
5
6
7
8
9
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
          

                
                                
                         

                    
                       
             
 









                                                     








                                            




                                                           
                                                                                    
                                                          




                                                                     



                                     





                                               
                                                      




































































































                                                                                              
                                                      
                                  
                          

                                                 

                                                





                                                                 
                                                                      
                                           


                                                                         
                                                                      
                                        

                                    
                                        



                                                              
                                                              



                                                                   





                                                                                                     


                                                                          




                                                                      









                                                    

                                                 



                                                             
                                                






                                                    
                                                





                                            

                                                 
 
                                                             

                                                             
                                                





                                                    


                                            

                                                              






                                            

                                                              



                                            

                                                             
 


                                          
                            

                                            

                                                             












                                               



































                                               
















                                               





















                                                                



















                                                                                                                           
                                                      
                                   
                          


























                                                                           
 

                                                 


                                                                               





                                                                                                                      
 


                                                                                      


                                                                                                                              
 


                                                                                   




                                                                                                                  
 




                                                                                                                                           









                                            
 





                                                         
 




                                                        
 

                                                        
 






                                              
 











                                                               
 



                                                                        
 











                                                          
 






                                                          
 
                                                          

                                                              

                                                                           

                                           
                 






                                                   
                                                                    

                                            
                            
                                                  
                         


                                                   
                                         

                        


                                                   
                                         

                      
                                     




                                                     


                                                              
 
                                                          

                                                              





























                                                                          
                                    









                                                              

                                                              























                                                                                       
                                                 







                                                            
 





























































                                                                                   





























































                                                                                                   
                                                      
                                      


















































































                                                                      
 


                                  
 


















































































                                                                             
                                                      













































                                                 
                            




                                    
 



                                                                   
 


                                                                                        
 


                                                                                                              
 


                                                                   
 


                                                                                               
 





                                                                                                      
 










                                                                    
                                             

                 
                                                                                         


                                                                 
                                                          















                                                                                       
                                                      























                                                                                 
                                                      
                                 
 
                                    



                                                              










                                                        
 


                                                            
 
                          



                                                              
 



                                                             
                                                 















                                                              
                                                 
 
                                





































                                                              
                                                                          

                            
                                                                           


                                   


                                                                                
 


























































































































































                                                                                                                 
                                                   






                                                                                                      










                                                                                        










                                                                                        











                                                                                                         












                                                                                                                        










                                                                                                      



                                       
 
 






















                                                                                                             
                         





                                                                                                                 
 




























































































































































































































































































































































                                                                                                                                              
                                                                  

                                      


                                               


                                               


                                      


                                               


                                      


                                                  



                                         





                                             


























                                            





                                            
                                    
                                          








                                                                          
                                                              
                                                      
                                                                                             
                      
                                         
 
                                             




                                                                              
                                         


                                                                                  
 


                                                                                  
 


                                                                                  
 

                                                                                  
                                                     
 


                                                                                  
 


                                                                                  

                  
                                                                                                             
 
                                                                     






                                                             
                                                                                       

                                           
                                                
                                                         

                                                     
                                                








                                                         
                                               

                  
                                         



                                                      







                                                                                                   

                                                                 
                                               







                                                    



                                                   


                                          



                                                 


                                        



                                               


                                      



                                               


                                      
                                                              
 
                                                                                             
                      
                                                                                 
 
                                             





                                                                                    



                                                                                  
 


                                                                                  


                                       


                                                                                  
 


                                                                                  
 


                                                                                  
 


                                                                                  

                  
                                                                                                
 
                                                                     

                                                                                                

                                                        
                                                          
 
                                                                                               
                                                         












                                                                                    
 



                                 

                  
                                           

                                                                 
                      
                                               


                                        

                          
import sys
import portfolio
import unittest
from decimal import Decimal as D
from unittest import mock
import requests
import requests_mock
from io import StringIO
import helper

limits = ["acceptance", "unit"]
for test_type in limits:
    if "--no{}".format(test_type) in sys.argv:
        sys.argv.remove("--no{}".format(test_type))
        limits.remove(test_type)
    if "--only{}".format(test_type) in sys.argv:
        sys.argv.remove("--only{}".format(test_type))
        limits = [test_type]
        break

class WebMockTestCase(unittest.TestCase):
    import time

    def setUp(self):
        super(WebMockTestCase, self).setUp()
        self.wm = requests_mock.Mocker()
        self.wm.start()

        self.patchers = [
                mock.patch.multiple(portfolio.BalanceStore,
                    all={},),
                mock.patch.multiple(portfolio.TradeStore,
                    all=[],
                    debug=False),
                mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
                mock.patch.multiple(portfolio.Computation,
                    computations=portfolio.Computation.computations),
                mock.patch.multiple(helper,
                    fees_cache={},
                    ticker_cache={},
                    ticker_cache_timestamp=self.time.time()),
                ]
        for patcher in self.patchers:
            patcher.start()

    def tearDown(self):
        for patcher in self.patchers:
            patcher.stop()
        self.wm.stop()
        super(WebMockTestCase, self).tearDown()

@unittest.skipUnless("unit" in limits, "Unit skipped")
class PortfolioTest(WebMockTestCase):
    def fill_data(self):
        if self.json_response is not None:
            portfolio.Portfolio.data = self.json_response

    def setUp(self):
        super(PortfolioTest, self).setUp()

        with open("test_portfolio.json") as example:
            self.json_response = example.read()

        self.wm.get(portfolio.Portfolio.URL, text=self.json_response)

    def test_get_cryptoportfolio(self):
        self.wm.get(portfolio.Portfolio.URL, [
            {"text":'{ "foo": "bar" }', "status_code": 200},
            {"text": "System Error", "status_code": 500},
            {"exc": requests.exceptions.ConnectTimeout},
            ])
        portfolio.Portfolio.get_cryptoportfolio()
        self.assertIn("foo", portfolio.Portfolio.data)
        self.assertEqual("bar", portfolio.Portfolio.data["foo"])
        self.assertTrue(self.wm.called)
        self.assertEqual(1, self.wm.call_count)

        portfolio.Portfolio.get_cryptoportfolio()
        self.assertIsNone(portfolio.Portfolio.data)
        self.assertEqual(2, self.wm.call_count)

        portfolio.Portfolio.data = "Foo"
        portfolio.Portfolio.get_cryptoportfolio()
        self.assertEqual("Foo", portfolio.Portfolio.data)
        self.assertEqual(3, self.wm.call_count)

    def test_parse_cryptoportfolio(self):
        portfolio.Portfolio.parse_cryptoportfolio()

        self.assertListEqual(
                ["medium", "high"],
                list(portfolio.Portfolio.liquidities.keys()))

        liquidities = portfolio.Portfolio.liquidities
        self.assertEqual(10, len(liquidities["medium"].keys()))
        self.assertEqual(10, len(liquidities["high"].keys()))

        expected = {
                'BTC':  (D("0.2857"), "long"),
                'DGB':  (D("0.1015"), "long"),
                'DOGE': (D("0.1805"), "long"),
                'SC':   (D("0.0623"), "long"),
                'ZEC':  (D("0.3701"), "long"),
                }
        self.assertDictEqual(expected, liquidities["high"]['2018-01-08'])

        expected = {
                'BTC':  (D("1.1102e-16"), "long"),
                'ETC':  (D("0.1"), "long"),
                'FCT':  (D("0.1"), "long"),
                'GAS':  (D("0.1"), "long"),
                'NAV':  (D("0.1"), "long"),
                'OMG':  (D("0.1"), "long"),
                'OMNI': (D("0.1"), "long"),
                'PPC':  (D("0.1"), "long"),
                'RIC':  (D("0.1"), "long"),
                'VIA':  (D("0.1"), "long"),
                'XCP':  (D("0.1"), "long"),
                }
        self.assertDictEqual(expected, liquidities["medium"]['2018-01-08'])

        # It doesn't refetch the data when available
        portfolio.Portfolio.parse_cryptoportfolio()

        self.assertEqual(1, self.wm.call_count)

    def test_repartition(self):
        expected_medium = {
                'BTC':   (D("1.1102e-16"), "long"),
                'USDT':  (D("0.1"), "long"),
                'ETC':   (D("0.1"), "long"),
                'FCT':   (D("0.1"), "long"),
                'OMG':   (D("0.1"), "long"),
                'STEEM': (D("0.1"), "long"),
                'STRAT': (D("0.1"), "long"),
                'XEM':   (D("0.1"), "long"),
                'XMR':   (D("0.1"), "long"),
                'XVC':   (D("0.1"), "long"),
                'ZRX':   (D("0.1"), "long"),
                }
        expected_high = {
                'USDT': (D("0.1226"), "long"),
                'BTC':  (D("0.1429"), "long"),
                'ETC':  (D("0.1127"), "long"),
                'ETH':  (D("0.1569"), "long"),
                'FCT':  (D("0.3341"), "long"),
                'GAS':  (D("0.1308"), "long"),
                }

        self.assertEqual(expected_medium, portfolio.Portfolio.repartition())
        self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium"))
        self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high"))

@unittest.skipUnless("unit" in limits, "Unit skipped")
class AmountTest(WebMockTestCase):
    def test_values(self):
        amount = portfolio.Amount("BTC", "0.65")
        self.assertEqual(D("0.65"), amount.value)
        self.assertEqual("BTC", amount.currency)

    def test_in_currency(self):
        amount = portfolio.Amount("ETC", 10)

        self.assertEqual(amount, amount.in_currency("ETC", None))

        ticker_mock = unittest.mock.Mock()
        with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
            ticker_mock.return_value = None

            self.assertRaises(Exception, amount.in_currency, "ETH", None)

        with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
            ticker_mock.return_value = {
                    "bid": D("0.2"),
                    "ask": D("0.4"),
                    "average": D("0.3"),
                    "foo": "bar",
                    }
            converted_amount = amount.in_currency("ETH", None)

            self.assertEqual(D("3.0"), converted_amount.value)
            self.assertEqual("ETH", converted_amount.currency)
            self.assertEqual(amount, converted_amount.linked_to)
            self.assertEqual("bar", converted_amount.ticker["foo"])

            converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
            self.assertEqual(D("2"), converted_amount.value)

            converted_amount = amount.in_currency("ETH", None, compute_value="ask")
            self.assertEqual(D("4"), converted_amount.value)

        converted_amount = amount.in_currency("ETH", None, rate=D("0.02"))
        self.assertEqual(D("0.2"), converted_amount.value)

    def test__round(self):
        amount = portfolio.Amount("BAR", portfolio.D("1.23456789876"))
        self.assertEqual(D("1.23456789"), round(amount).value)
        self.assertEqual(D("1.23"), round(amount, 2).value)

    def test__abs(self):
        amount = portfolio.Amount("SC", -120)
        self.assertEqual(120, abs(amount).value)
        self.assertEqual("SC", abs(amount).currency)

        amount = portfolio.Amount("SC", 10)
        self.assertEqual(10, abs(amount).value)
        self.assertEqual("SC", abs(amount).currency)

    def test__add(self):
        amount1 = portfolio.Amount("XVG", "12.9")
        amount2 = portfolio.Amount("XVG", "13.1")

        self.assertEqual(26, (amount1 + amount2).value)
        self.assertEqual("XVG", (amount1 + amount2).currency)

        amount3 = portfolio.Amount("ETH", "1.6")
        with self.assertRaises(Exception):
            amount1 + amount3

        amount4 = portfolio.Amount("ETH", 0.0)
        self.assertEqual(amount1, amount1 + amount4)

    def test__radd(self):
        amount = portfolio.Amount("XVG", "12.9")

        self.assertEqual(amount, 0 + amount)
        with self.assertRaises(Exception):
            4 + amount

    def test__sub(self):
        amount1 = portfolio.Amount("XVG", "13.3")
        amount2 = portfolio.Amount("XVG", "13.1")

        self.assertEqual(D("0.2"), (amount1 - amount2).value)
        self.assertEqual("XVG", (amount1 - amount2).currency)

        amount3 = portfolio.Amount("ETH", "1.6")
        with self.assertRaises(Exception):
            amount1 - amount3

        amount4 = portfolio.Amount("ETH", 0.0)
        self.assertEqual(amount1, amount1 - amount4)

    def test__mul(self):
        amount = portfolio.Amount("XEM", 11)

        self.assertEqual(D("38.5"), (amount * D("3.5")).value)
        self.assertEqual(D("33"), (amount * 3).value)

        with self.assertRaises(Exception):
            amount * amount

    def test__rmul(self):
        amount = portfolio.Amount("XEM", 11)

        self.assertEqual(D("38.5"), (D("3.5") * amount).value)
        self.assertEqual(D("33"), (3 * amount).value)

    def test__floordiv(self):
        amount = portfolio.Amount("XEM", 11)

        self.assertEqual(D("5.5"), (amount / 2).value)
        self.assertEqual(D("4.4"), (amount / D("2.5")).value)

        with self.assertRaises(Exception):
            amount / amount

    def test__truediv(self):
        amount = portfolio.Amount("XEM", 11)

        self.assertEqual(D("5.5"), (amount / 2).value)
        self.assertEqual(D("4.4"), (amount / D("2.5")).value)

    def test__lt(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)

        self.assertTrue(amount1 < amount2)
        self.assertFalse(amount2 < amount1)
        self.assertFalse(amount1 < amount1)

        amount3 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount1 < amount3

    def test__le(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)

        self.assertTrue(amount1 <= amount2)
        self.assertFalse(amount2 <= amount1)
        self.assertTrue(amount1 <= amount1)

        amount3 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount1 <= amount3

    def test__gt(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)

        self.assertTrue(amount2 > amount1)
        self.assertFalse(amount1 > amount2)
        self.assertFalse(amount1 > amount1)

        amount3 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount3 > amount1

    def test__ge(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)

        self.assertTrue(amount2 >= amount1)
        self.assertFalse(amount1 >= amount2)
        self.assertTrue(amount1 >= amount1)

        amount3 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount3 >= amount1

    def test__eq(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)
        amount3 = portfolio.Amount("BTD", 11.3)

        self.assertFalse(amount1 == amount2)
        self.assertFalse(amount2 == amount1)
        self.assertTrue(amount1 == amount3)
        self.assertFalse(amount2 == 0)

        amount4 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount1 == amount4

        amount5 = portfolio.Amount("BTD", 0)
        self.assertTrue(amount5 == 0)

    def test__ne(self):
        amount1 = portfolio.Amount("BTD", 11.3)
        amount2 = portfolio.Amount("BTD", 13.1)
        amount3 = portfolio.Amount("BTD", 11.3)

        self.assertTrue(amount1 != amount2)
        self.assertTrue(amount2 != amount1)
        self.assertFalse(amount1 != amount3)
        self.assertTrue(amount2 != 0)

        amount4 = portfolio.Amount("BTC", 1.6)
        with self.assertRaises(Exception):
            amount1 != amount4

        amount5 = portfolio.Amount("BTD", 0)
        self.assertFalse(amount5 != 0)

    def test__neg(self):
        amount1 = portfolio.Amount("BTD", "11.3")

        self.assertEqual(portfolio.D("-11.3"), (-amount1).value)

    def test__str(self):
        amount1 = portfolio.Amount("BTX", 32)
        self.assertEqual("32.00000000 BTX", str(amount1))

        amount2 = portfolio.Amount("USDT", 12000)
        amount1.linked_to = amount2
        self.assertEqual("32.00000000 BTX [12000.00000000 USDT]", str(amount1))

    def test__repr(self):
        amount1 = portfolio.Amount("BTX", 32)
        self.assertEqual("Amount(32.00000000 BTX)", repr(amount1))

        amount2 = portfolio.Amount("USDT", 12000)
        amount1.linked_to = amount2
        self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT))", repr(amount1))

        amount3 = portfolio.Amount("BTC", 0.1)
        amount2.linked_to = amount3
        self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))

@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceTest(WebMockTestCase):
    def test_values(self):
        balance = portfolio.Balance("BTC", {
            "exchange_total": "0.65",
            "exchange_free": "0.35",
            "exchange_used": "0.30",
            "margin_total": "-10",
            "margin_borrowed": "-10",
            "margin_free": "0",
            "margin_position_type": "short",
            "margin_borrowed_base_currency": "USDT",
            "margin_liquidation_price": "1.20",
            "margin_pending_gain": "10",
            "margin_lending_fees": "0.4",
            "margin_borrowed_base_price": "0.15",
            })
        self.assertEqual(portfolio.D("0.65"), balance.exchange_total.value)
        self.assertEqual(portfolio.D("0.35"), balance.exchange_free.value)
        self.assertEqual(portfolio.D("0.30"), balance.exchange_used.value)
        self.assertEqual("BTC", balance.exchange_total.currency)
        self.assertEqual("BTC", balance.exchange_free.currency)
        self.assertEqual("BTC", balance.exchange_total.currency)

        self.assertEqual(portfolio.D("-10"), balance.margin_total.value)
        self.assertEqual(portfolio.D("-10"), balance.margin_borrowed.value)
        self.assertEqual(portfolio.D("0"), balance.margin_free.value)
        self.assertEqual("BTC", balance.margin_total.currency)
        self.assertEqual("BTC", balance.margin_borrowed.currency)
        self.assertEqual("BTC", balance.margin_free.currency)

        self.assertEqual("BTC", balance.currency)

        self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
        self.assertEqual("USDT", balance.margin_lending_fees.currency)

    def test__repr(self):
        self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
                repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
        balance = portfolio.Balance("BTX", { "exchange_total": 3,
            "exchange_used": 1, "exchange_free": 2 })
        self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))

        balance = portfolio.Balance("BTX", { "exchange_total": 1, "exchange_used": 1})
        self.assertEqual("Balance(BTX Exch: [❌1.00000000 BTX])", repr(balance))

        balance = portfolio.Balance("BTX", { "margin_total": 3,
            "margin_borrowed": 1, "margin_free": 2 })
        self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance))

        balance = portfolio.Balance("BTX", { "margin_total": 2, "margin_free": 2 })
        self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX])", repr(balance))

        balance = portfolio.Balance("BTX", { "margin_total": -3,
            "margin_borrowed_base_price": D("0.1"),
            "margin_borrowed_base_currency": "BTC",
            "margin_lending_fees": D("0.002") })
        self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))

        balance = portfolio.Balance("BTX", { "margin_total": 1,
            "margin_borrowed": 1, "exchange_free": 2, "exchange_total": 2})
        self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))

@unittest.skipUnless("unit" in limits, "Unit skipped")
class HelperTest(WebMockTestCase):
    def test_get_ticker(self):
        market = mock.Mock()
        market.fetch_ticker.side_effect = [
                { "bid": 1, "ask": 3 },
                helper.ExchangeError("foo"),
                { "bid": 10, "ask": 40 },
                helper.ExchangeError("foo"),
                helper.ExchangeError("foo"),
                ]

        ticker = helper.get_ticker("ETH", "ETC", market)
        market.fetch_ticker.assert_called_with("ETH/ETC")
        self.assertEqual(1, ticker["bid"])
        self.assertEqual(3, ticker["ask"])
        self.assertEqual(2, ticker["average"])
        self.assertFalse(ticker["inverted"])

        ticker = helper.get_ticker("ETH", "XVG", market)
        self.assertEqual(0.0625, ticker["average"])
        self.assertTrue(ticker["inverted"])
        self.assertIn("original", ticker)
        self.assertEqual(10, ticker["original"]["bid"])

        ticker = helper.get_ticker("XVG", "XMR", market)
        self.assertIsNone(ticker)

        market.fetch_ticker.assert_has_calls([
            mock.call("ETH/ETC"),
            mock.call("ETH/XVG"),
            mock.call("XVG/ETH"),
            mock.call("XVG/XMR"),
            mock.call("XMR/XVG"),
            ])

        market2 = mock.Mock()
        market2.fetch_ticker.side_effect = [
                { "bid": 1, "ask": 3 },
                { "bid": 1.2, "ask": 3.5 },
                ]
        ticker1 = helper.get_ticker("ETH", "ETC", market2)
        ticker2 = helper.get_ticker("ETH", "ETC", market2)
        ticker3 = helper.get_ticker("ETC", "ETH", market2)
        market2.fetch_ticker.assert_called_once_with("ETH/ETC")
        self.assertEqual(1, ticker1["bid"])
        self.assertDictEqual(ticker1, ticker2)
        self.assertDictEqual(ticker1, ticker3["original"])

        ticker4 = helper.get_ticker("ETH", "ETC", market2, refresh=True)
        ticker5 = helper.get_ticker("ETH", "ETC", market2)
        self.assertEqual(1.2, ticker4["bid"])
        self.assertDictEqual(ticker4, ticker5)

        market3 = mock.Mock()
        market3.fetch_ticker.side_effect = [
                { "bid": 1, "ask": 3 },
                { "bid": 1.2, "ask": 3.5 },
                ]
        ticker6 = helper.get_ticker("ETH", "ETC", market3)
        helper.ticker_cache_timestamp -= 4
        ticker7 = helper.get_ticker("ETH", "ETC", market3)
        helper.ticker_cache_timestamp -= 2
        ticker8 = helper.get_ticker("ETH", "ETC", market3)
        self.assertDictEqual(ticker6, ticker7)
        self.assertEqual(1.2, ticker8["bid"])

    def test_fetch_fees(self):
        market = mock.Mock()
        market.fetch_fees.return_value = "Foo"
        self.assertEqual("Foo", helper.fetch_fees(market))
        market.fetch_fees.assert_called_once()
        self.assertEqual("Foo", helper.fetch_fees(market))
        market.fetch_fees.assert_called_once()

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.TradeStore, "compute_trades")
    def test_prepare_trades(self, compute_trades, get_ticker, repartition):
        repartition.return_value = {
                "XEM": (D("0.75"), "long"),
                "BTC": (D("0.25"), "long"),
                }
        def _get_ticker(c1, c2, market):
            if c1 == "USDT" and c2 == "BTC":
                return { "average": D("0.0001") }
            if c1 == "XVG" and c2 == "BTC":
                return { "average": D("0.000001") }
            if c1 == "XEM" and c2 == "BTC":
                return { "average": D("0.001") }
            self.fail("Should be called with {}, {}".format(c1, c2))
        get_ticker.side_effect = _get_ticker

        market = mock.Mock()
        market.fetch_all_balances.return_value = {
                "USDT": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                "XVG": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                }
        helper.prepare_trades(market)
        compute_trades.assert_called()

        call = compute_trades.call_args
        self.assertEqual(market, call[1]["market"])
        self.assertEqual(1, call[0][0]["USDT"].value)
        self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
        self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
        self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.TradeStore, "compute_trades")
    def test_update_trades(self, compute_trades, get_ticker, repartition):
        repartition.return_value = {
                "XEM": (D("0.75"), "long"),
                "BTC": (D("0.25"), "long"),
                }
        def _get_ticker(c1, c2, market):
            if c1 == "USDT" and c2 == "BTC":
                return { "average": D("0.0001") }
            if c1 == "XVG" and c2 == "BTC":
                return { "average": D("0.000001") }
            if c1 == "XEM" and c2 == "BTC":
                return { "average": D("0.001") }
            self.fail("Should be called with {}, {}".format(c1, c2))
        get_ticker.side_effect = _get_ticker

        market = mock.Mock()
        market.fetch_all_balances.return_value = {
                "USDT": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                "XVG": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                }
        helper.update_trades(market)
        compute_trades.assert_called()

        call = compute_trades.call_args
        self.assertEqual(market, call[1]["market"])
        self.assertEqual(1, call[0][0]["USDT"].value)
        self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
        self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
        self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.TradeStore, "compute_trades")
    def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
        def _get_ticker(c1, c2, market):
            if c1 == "USDT" and c2 == "BTC":
                return { "average": D("0.0001") }
            if c1 == "XVG" and c2 == "BTC":
                return { "average": D("0.000001") }
            self.fail("Should be called with {}, {}".format(c1, c2))
        get_ticker.side_effect = _get_ticker

        market = mock.Mock()
        market.fetch_all_balances.return_value = {
                "USDT": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                "XVG": {
                    "exchange_free": D("10000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("10000.0"),
                    "total": D("10000.0")
                    },
                }
        helper.prepare_trades_to_sell_all(market)
        repartition.assert_not_called()
        compute_trades.assert_called()

        call = compute_trades.call_args
        self.assertEqual(market, call[1]["market"])
        self.assertEqual(1, call[0][0]["USDT"].value)
        self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
        self.assertEqual(D("1.01"), call[0][1]["BTC"].value)

    @mock.patch.object(portfolio.time, "sleep")
    @mock.patch.object(portfolio.TradeStore, "all_orders")
    def test_follow_orders(self, all_orders, time_mock):
        for verbose, debug, sleep in [
                (True, False, None), (False, False, None),
                (True, True, None), (True, False, 12),
                (True, True, 12)]:
            with self.subTest(sleep=sleep, debug=debug, verbose=verbose), \
                    mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
                portfolio.TradeStore.debug = debug
                order_mock1 = mock.Mock()
                order_mock2 = mock.Mock()
                order_mock3 = mock.Mock()
                all_orders.side_effect = [
                        [order_mock1, order_mock2],
                        [order_mock1, order_mock2],

                        [order_mock1, order_mock3],
                        [order_mock1, order_mock3],

                        [order_mock1, order_mock3],
                        [order_mock1, order_mock3],

                        []
                        ]

                order_mock1.get_status.side_effect = ["open", "open", "closed"]
                order_mock2.get_status.side_effect = ["open"]
                order_mock3.get_status.side_effect = ["open", "closed"]

                order_mock1.trade = mock.Mock()
                order_mock2.trade = mock.Mock()
                order_mock3.trade = mock.Mock()

                helper.follow_orders(verbose=verbose, sleep=sleep)

                order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
                order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
                self.assertEqual(2, order_mock1.trade.update_order.call_count)
                self.assertEqual(3, order_mock1.get_status.call_count)

                order_mock2.trade.update_order.assert_any_call(order_mock2, 1)
                self.assertEqual(1, order_mock2.trade.update_order.call_count)
                self.assertEqual(1, order_mock2.get_status.call_count)

                order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
                self.assertEqual(1, order_mock3.trade.update_order.call_count)
                self.assertEqual(2, order_mock3.get_status.call_count)

                if sleep is None:
                    if debug:
                        time_mock.assert_called_with(7)
                    else:
                        time_mock.assert_called_with(30)
                else:
                    time_mock.assert_called_with(sleep)

                if verbose:
                    self.assertNotEqual("", stdout_mock.getvalue())
                else:
                    self.assertEqual("", stdout_mock.getvalue())

    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    def test_move_balance(self, fetch_balances):
        for debug in [True, False]:
            with self.subTest(debug=debug),\
                    mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
                value_from = portfolio.Amount("BTC", "1.0")
                value_from.linked_to = portfolio.Amount("ETH", "10.0")
                value_to = portfolio.Amount("BTC", "10.0")
                trade1 = portfolio.Trade(value_from, value_to, "ETH")

                value_from = portfolio.Amount("BTC", "0.0")
                value_from.linked_to = portfolio.Amount("ETH", "0.0")
                value_to = portfolio.Amount("BTC", "-3.0")
                trade2 = portfolio.Trade(value_from, value_to, "ETH")

                value_from = portfolio.Amount("USDT", "0.0")
                value_from.linked_to = portfolio.Amount("XVG", "0.0")
                value_to = portfolio.Amount("USDT", "-50.0")
                trade3 = portfolio.Trade(value_from, value_to, "XVG")

                portfolio.TradeStore.all = [trade1, trade2, trade3]
                balance1 = portfolio.Balance("BTC", { "margin_free": "0" })
                balance2 = portfolio.Balance("USDT", { "margin_free": "100" })
                portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2}

                market = mock.Mock()

                helper.move_balances(market, debug=debug)

                fetch_balances.assert_called_with(market)
                if debug:
                    self.assertRegex(stdout_mock.getvalue(), "market.transfer_balance")
                else:
                    market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
                    market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")

    @mock.patch.object(helper, "prepare_trades")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
    @mock.patch('sys.stdout', new_callable=StringIO)
    def test_print_orders(self, stdout_mock, print_all_with_order, prepare_orders, prepare_trades):
        market = mock.Mock()
        portfolio.BalanceStore.all = {
                "BTC": portfolio.Balance("BTC", {
                    "total": "0.65",
                    "exchange_total":"0.65",
                    "exchange_free": "0.35",
                    "exchange_used": "0.30"}),
                "ETH": portfolio.Balance("ETH", {
                    "total": 3,
                    "exchange_total": 3,
                    "exchange_free": 3,
                    "exchange_used": 0}),
                }
        helper.print_orders(market)
        prepare_trades.assert_called_with(market, base_currency="BTC",
                compute_value="average")
        prepare_orders.assert_called_with(compute_value="average")
        print_all_with_order.assert_called()
        self.assertRegex(stdout_mock.getvalue(), "Balance")


@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeStoreTest(WebMockTestCase):
    @mock.patch.object(portfolio.BalanceStore, "currencies")
    @mock.patch.object(portfolio.TradeStore, "add_trade_if_matching")
    def test_compute_trades(self, add_trade_if_matching, currencies):
        currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]

        values_in_base = {
                "XMR": portfolio.Amount("BTC", D("0.9")),
                "DASH": portfolio.Amount("BTC", D("0.4")),
                "XVG": portfolio.Amount("BTC", D("-0.5")),
                "BTC": portfolio.Amount("BTC", D("0.5")),
                }
        new_repartition = {
                "DASH": portfolio.Amount("BTC", D("0.5")),
                "XVG": portfolio.Amount("BTC", D("0.1")),
                "BTC": portfolio.Amount("BTC", D("0.4")),
                "ETH": portfolio.Amount("BTC", D("0.3")),
                }

        portfolio.TradeStore.compute_trades(values_in_base,
                new_repartition, only="only", market="market")

        self.assertEqual(5, add_trade_if_matching.call_count)
        add_trade_if_matching.assert_any_call(
                portfolio.Amount("BTC", D("0.9")),
                portfolio.Amount("BTC", 0),
                "XMR", only="only", market="market"
                )
        add_trade_if_matching.assert_any_call(
                portfolio.Amount("BTC", D("0.4")),
                portfolio.Amount("BTC", D("0.5")),
                "DASH", only="only", market="market"
                )
        add_trade_if_matching.assert_any_call(
                portfolio.Amount("BTC", D("-0.5")),
                portfolio.Amount("BTC", D("0")),
                "XVG", only="only", market="market"
                )
        add_trade_if_matching.assert_any_call(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.1")),
                "XVG", only="only", market="market"
                )
        add_trade_if_matching.assert_any_call(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only="only", market="market"
                )

    def test_add_trade_if_matching(self):
        result = portfolio.TradeStore.add_trade_if_matching(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only="nope", market="market"
                )
        self.assertEqual(0, len(portfolio.TradeStore.all))
        self.assertEqual(False, result)

        portfolio.TradeStore.all = []
        result = portfolio.TradeStore.add_trade_if_matching(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only=None, market="market"
                )
        self.assertEqual(1, len(portfolio.TradeStore.all))
        self.assertEqual(True, result)

        portfolio.TradeStore.all = []
        result = portfolio.TradeStore.add_trade_if_matching(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only="acquire", market="market"
                )
        self.assertEqual(1, len(portfolio.TradeStore.all))
        self.assertEqual(True, result)

        portfolio.TradeStore.all = []
        result = portfolio.TradeStore.add_trade_if_matching(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only="dispose", market="market"
                )
        self.assertEqual(0, len(portfolio.TradeStore.all))
        self.assertEqual(False, result)

    def test_prepare_orders(self):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()

        portfolio.TradeStore.all.append(trade_mock1)
        portfolio.TradeStore.all.append(trade_mock2)

        portfolio.TradeStore.prepare_orders()
        trade_mock1.prepare_order.assert_called_with(compute_value="default")
        trade_mock2.prepare_order.assert_called_with(compute_value="default")

        portfolio.TradeStore.prepare_orders(compute_value="bla")
        trade_mock1.prepare_order.assert_called_with(compute_value="bla")
        trade_mock2.prepare_order.assert_called_with(compute_value="bla")

        trade_mock1.prepare_order.reset_mock()
        trade_mock2.prepare_order.reset_mock()

        trade_mock1.action = "foo"
        trade_mock2.action = "bar"
        portfolio.TradeStore.prepare_orders(only="bar")
        trade_mock1.prepare_order.assert_not_called()
        trade_mock2.prepare_order.assert_called_with(compute_value="default")

    def test_print_all_with_order(self):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()
        trade_mock3 = mock.Mock()
        portfolio.TradeStore.all = [trade_mock1, trade_mock2, trade_mock3]

        portfolio.TradeStore.print_all_with_order()

        trade_mock1.print_with_order.assert_called()
        trade_mock2.print_with_order.assert_called()
        trade_mock3.print_with_order.assert_called()

    @mock.patch.object(portfolio.TradeStore, "all_orders")
    def test_run_orders(self, all_orders):
        order_mock1 = mock.Mock()
        order_mock2 = mock.Mock()
        order_mock3 = mock.Mock()
        all_orders.return_value = [order_mock1, order_mock2, order_mock3]
        portfolio.TradeStore.run_orders()
        all_orders.assert_called_with(state="pending")

        order_mock1.run.assert_called()
        order_mock2.run.assert_called()
        order_mock3.run.assert_called()

    def test_all_orders(self):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()

        order_mock1 = mock.Mock()
        order_mock2 = mock.Mock()
        order_mock3 = mock.Mock()

        trade_mock1.orders = [order_mock1, order_mock2]
        trade_mock2.orders = [order_mock3]

        order_mock1.status = "pending"
        order_mock2.status = "open"
        order_mock3.status = "open"

        portfolio.TradeStore.all.append(trade_mock1)
        portfolio.TradeStore.all.append(trade_mock2)

        orders = portfolio.TradeStore.all_orders()
        self.assertEqual(3, len(orders))

        open_orders = portfolio.TradeStore.all_orders(state="open")
        self.assertEqual(2, len(open_orders))
        self.assertEqual([order_mock2, order_mock3], open_orders)

    @mock.patch.object(portfolio.TradeStore, "all_orders")
    def test_update_all_orders_status(self, all_orders):
        order_mock1 = mock.Mock()
        order_mock2 = mock.Mock()
        order_mock3 = mock.Mock()
        all_orders.return_value = [order_mock1, order_mock2, order_mock3]
        portfolio.TradeStore.update_all_orders_status()
        all_orders.assert_called_with(state="open")

        order_mock1.get_status.assert_called()
        order_mock2.get_status.assert_called()
        order_mock3.get_status.assert_called()

@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceStoreTest(WebMockTestCase):
    def setUp(self):
        super(BalanceStoreTest, self).setUp()

        self.fetch_balance = {
                "ETC": {
                    "exchange_free": 0,
                    "exchange_used": 0,
                    "exchange_total": 0,
                    "margin_total": 0,
                    },
                "USDT": {
                    "exchange_free": D("6.0"),
                    "exchange_used": D("1.2"),
                    "exchange_total": D("7.2"),
                    "margin_total": 0,
                    },
                "XVG": {
                    "exchange_free": 16,
                    "exchange_used": 0,
                    "exchange_total": 16,
                    "margin_total": 0,
                    },
                "XMR": {
                    "exchange_free": 0,
                    "exchange_used": 0,
                    "exchange_total": 0,
                    "margin_total": D("-1.0"),
                    "margin_free": 0,
                    },
                }

    @mock.patch.object(helper, "get_ticker")
    def test_in_currency(self, get_ticker):
        portfolio.BalanceStore.all = {
                "BTC": portfolio.Balance("BTC", {
                    "total": "0.65",
                    "exchange_total":"0.65",
                    "exchange_free": "0.35",
                    "exchange_used": "0.30"}),
                "ETH": portfolio.Balance("ETH", {
                    "total": 3,
                    "exchange_total": 3,
                    "exchange_free": 3,
                    "exchange_used": 0}),
                }
        market = mock.Mock()
        get_ticker.return_value = {
                "bid": D("0.09"),
                "ask": D("0.11"),
                "average": D("0.1"),
                }

        amounts = portfolio.BalanceStore.in_currency("BTC", market)
        self.assertEqual("BTC", amounts["ETH"].currency)
        self.assertEqual(D("0.65"), amounts["BTC"].value)
        self.assertEqual(D("0.30"), amounts["ETH"].value)

        amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid")
        self.assertEqual(D("0.65"), amounts["BTC"].value)
        self.assertEqual(D("0.27"), amounts["ETH"].value)

        amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used")
        self.assertEqual(D("0.30"), amounts["BTC"].value)
        self.assertEqual(0, amounts["ETH"].value)

    def test_fetch_balances(self):
        market = mock.Mock()
        market.fetch_all_balances.return_value = self.fetch_balance

        portfolio.BalanceStore.fetch_balances(market)
        self.assertNotIn("ETC", portfolio.BalanceStore.currencies())
        self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.BalanceStore.currencies()))

        portfolio.BalanceStore.all["ETC"] = portfolio.Balance("ETC", {
            "exchange_total": "1", "exchange_free": "0",
            "exchange_used": "1" })
        portfolio.BalanceStore.fetch_balances(market)
        self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
        self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))

    @mock.patch.object(portfolio.Portfolio, "repartition")
    def test_dispatch_assets(self, repartition):
        market = mock.Mock()
        market.fetch_all_balances.return_value = self.fetch_balance
        portfolio.BalanceStore.fetch_balances(market)

        self.assertNotIn("XEM", portfolio.BalanceStore.currencies())

        repartition.return_value = {
                "XEM": (D("0.75"), "long"),
                "BTC": (D("0.26"), "long"),
                "DASH": (D("0.10"), "short"),
                }

        amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
        self.assertIn("XEM", portfolio.BalanceStore.currencies())
        self.assertEqual(D("2.6"), amounts["BTC"].value)
        self.assertEqual(D("7.5"), amounts["XEM"].value)
        self.assertEqual(D("-1.0"), amounts["DASH"].value)

    def test_currencies(self):
        portfolio.BalanceStore.all = {
                "BTC": portfolio.Balance("BTC", {
                    "total": "0.65",
                    "exchange_total":"0.65",
                    "exchange_free": "0.35",
                    "exchange_used": "0.30"}),
                "ETH": portfolio.Balance("ETH", {
                    "total": 3,
                    "exchange_total": 3,
                    "exchange_free": 3,
                    "exchange_used": 0}),
                }
        self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))

@unittest.skipUnless("unit" in limits, "Unit skipped")
class ComputationTest(WebMockTestCase):
    def test_compute_value(self):
        compute = mock.Mock()
        portfolio.Computation.compute_value("foo", "buy", compute_value=compute)
        compute.assert_called_with("foo", "ask")

        compute.reset_mock()
        portfolio.Computation.compute_value("foo", "sell", compute_value=compute)
        compute.assert_called_with("foo", "bid")

        compute.reset_mock()
        portfolio.Computation.compute_value("foo", "ask", compute_value=compute)
        compute.assert_called_with("foo", "ask")

        compute.reset_mock()
        portfolio.Computation.compute_value("foo", "bid", compute_value=compute)
        compute.assert_called_with("foo", "bid")

        compute.reset_mock()
        portfolio.Computation.computations["test"] = compute
        portfolio.Computation.compute_value("foo", "bid", compute_value="test")
        compute.assert_called_with("foo", "bid")


@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeTest(WebMockTestCase):

    def test_values_assertion(self):
        value_from = portfolio.Amount("BTC", "1.0")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")
        self.assertEqual("BTC", trade.base_currency)
        self.assertEqual("ETH", trade.currency)

        with self.assertRaises(AssertionError):
            portfolio.Trade(value_from, value_to, "ETC")
        with self.assertRaises(AssertionError):
            value_from.linked_to = None
            portfolio.Trade(value_from, value_to, "ETH")
        with self.assertRaises(AssertionError):
            value_from.currency = "ETH"
            portfolio.Trade(value_from, value_to, "ETH")

        value_from = portfolio.Amount("BTC", 0)
        trade = portfolio.Trade(value_from, value_to, "ETH")
        self.assertEqual(0, trade.value_from.linked_to)

    def test_action(self):
        value_from = portfolio.Amount("BTC", "1.0")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertIsNone(trade.action)

        value_from = portfolio.Amount("BTC", "1.0")
        value_from.linked_to = portfolio.Amount("BTC", "1.0")
        value_to = portfolio.Amount("BTC", "2.0")
        trade = portfolio.Trade(value_from, value_to, "BTC")

        self.assertIsNone(trade.action)

        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("acquire", trade.action)

        value_from = portfolio.Amount("BTC", "0")
        value_from.linked_to = portfolio.Amount("ETH", "0")
        value_to = portfolio.Amount("BTC", "-1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("acquire", trade.action)

    def test_order_action(self):
        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("buy", trade.order_action(False))
        self.assertEqual("sell", trade.order_action(True))

        value_from = portfolio.Amount("BTC", "0")
        value_from.linked_to = portfolio.Amount("ETH", "0")
        value_to = portfolio.Amount("BTC", "-1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("sell", trade.order_action(False))
        self.assertEqual("buy", trade.order_action(True))

    def test_trade_type(self):
        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("long", trade.trade_type)

        value_from = portfolio.Amount("BTC", "0")
        value_from.linked_to = portfolio.Amount("ETH", "0")
        value_to = portfolio.Amount("BTC", "-1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("short", trade.trade_type)

    def test_filled_amount(self):
        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        order1 = mock.Mock()
        order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")

        order2 = mock.Mock()
        order2.filled_amount.return_value = portfolio.Amount("ETH", "0.01")
        trade.orders.append(order1)
        trade.orders.append(order2)

        self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount())
        order1.filled_amount.assert_called_with(in_base_currency=False)
        order2.filled_amount.assert_called_with(in_base_currency=False)

        self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=False))
        order1.filled_amount.assert_called_with(in_base_currency=False)
        order2.filled_amount.assert_called_with(in_base_currency=False)

        self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount(in_base_currency=True))
        order1.filled_amount.assert_called_with(in_base_currency=True)
        order2.filled_amount.assert_called_with(in_base_currency=True)

    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.Computation, "compute_value")
    @mock.patch.object(portfolio.Trade, "filled_amount")
    @mock.patch.object(portfolio, "Order")
    def test_prepare_order(self, Order, filled_amount, compute_value, get_ticker):
        Order.return_value = "Order"

        with self.subTest(desc="Nothing to do"):
            value_from = portfolio.Amount("BTC", "10")
            value_from.rate = D("0.1")
            value_from.linked_to = portfolio.Amount("FOO", "100")
            value_to = portfolio.Amount("BTC", "10")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order()

            filled_amount.assert_not_called()
            compute_value.assert_not_called()
            self.assertEqual(0, len(trade.orders))
            Order.assert_not_called()

        get_ticker.return_value = { "inverted": False }
        with self.subTest(desc="Already filled"), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            filled_amount.return_value = portfolio.Amount("FOO", "100")
            compute_value.return_value = D("0.125")

            value_from = portfolio.Amount("BTC", "10")
            value_from.rate = D("0.1")
            value_from.linked_to = portfolio.Amount("FOO", "100")
            value_to = portfolio.Amount("BTC", "0")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
            self.assertEqual(0, len(trade.orders))
            self.assertRegex(stdout_mock.getvalue(), "Less to do than already filled: ")
            Order.assert_not_called()

        with self.subTest(action="dispose", inverted=False):
            filled_amount.return_value = portfolio.Amount("FOO", "60")
            compute_value.return_value = D("0.125")

            value_from = portfolio.Amount("BTC", "10")
            value_from.rate = D("0.1")
            value_from.linked_to = portfolio.Amount("FOO", "100")
            value_to = portfolio.Amount("BTC", "1")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
            self.assertEqual(1, len(trade.orders))
            Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
                    D("0.125"), "BTC", "long", "market",
                    trade, close_if_possible=False)

        with self.subTest(action="acquire", inverted=False):
            filled_amount.return_value = portfolio.Amount("BTC", "3")
            compute_value.return_value = D("0.125")

            value_from = portfolio.Amount("BTC", "1")
            value_from.rate = D("0.1")
            value_from.linked_to = portfolio.Amount("FOO", "10")
            value_to = portfolio.Amount("BTC", "10")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=True)
            compute_value.assert_called_with(get_ticker.return_value, "buy", compute_value="default")
            self.assertEqual(1, len(trade.orders))

            Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
                D("0.125"), "BTC", "long", "market",
                trade, close_if_possible=False)

        with self.subTest(close_if_possible=True):
            filled_amount.return_value = portfolio.Amount("FOO", "0")
            compute_value.return_value = D("0.125")

            value_from = portfolio.Amount("BTC", "10")
            value_from.rate = D("0.1")
            value_from.linked_to = portfolio.Amount("FOO", "100")
            value_to = portfolio.Amount("BTC", "0")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
            self.assertEqual(1, len(trade.orders))
            Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
                    D("0.125"), "BTC", "long", "market",
                    trade, close_if_possible=True)

        get_ticker.return_value = { "inverted": True, "original": {} }
        with self.subTest(action="dispose", inverted=True):
            filled_amount.return_value = portfolio.Amount("FOO", "300")
            compute_value.return_value = D("125")

            value_from = portfolio.Amount("BTC", "10")
            value_from.rate = D("0.01")
            value_from.linked_to = portfolio.Amount("FOO", "1000")
            value_to = portfolio.Amount("BTC", "1")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order(compute_value="foo")

            filled_amount.assert_called_with(in_base_currency=True)
            compute_value.assert_called_with(get_ticker.return_value["original"], "buy", compute_value="foo")
            self.assertEqual(1, len(trade.orders))
            Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
                    D("125"), "FOO", "long", "market",
                    trade, close_if_possible=False)

        with self.subTest(action="acquire", inverted=True):
            filled_amount.return_value = portfolio.Amount("BTC", "4")
            compute_value.return_value = D("125")

            value_from = portfolio.Amount("BTC", "1")
            value_from.rate = D("0.01")
            value_from.linked_to = portfolio.Amount("FOO", "100")
            value_to = portfolio.Amount("BTC", "10")
            trade = portfolio.Trade(value_from, value_to, "FOO", market="market")

            trade.prepare_order(compute_value="foo")

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(get_ticker.return_value["original"], "sell", compute_value="foo")
            self.assertEqual(1, len(trade.orders))
            Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
                    D("125"), "FOO", "long", "market",
                    trade, close_if_possible=False)


    @mock.patch.object(portfolio.Trade, "prepare_order")
    def test_update_order(self, prepare_order):
        order_mock = mock.Mock()
        new_order_mock = mock.Mock()

        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")
        prepare_order.return_value = new_order_mock

        for i in [0, 1, 3, 4, 6]:
            with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                self.assertRegex(stdout_mock.getvalue(), "tick {}, waiting".format(i))

            order_mock.reset_mock()
            new_order_mock.reset_mock()
            trade.orders = []

        with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            trade.update_order(order_mock, 2)
            order_mock.cancel.assert_called()
            new_order_mock.run.assert_called()
            prepare_order.assert_called()
            self.assertRegex(stdout_mock.getvalue(), "tick 2, cancelling and adjusting")

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []

        with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            trade.update_order(order_mock, 5)
            order_mock.cancel.assert_called()
            new_order_mock.run.assert_called()
            prepare_order.assert_called()
            self.assertRegex(stdout_mock.getvalue(), "tick 5, cancelling and adjusting")

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []

        with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            trade.update_order(order_mock, 7)
            order_mock.cancel.assert_called()
            new_order_mock.run.assert_called()
            prepare_order.assert_called_with(compute_value="default")
            self.assertRegex(stdout_mock.getvalue(), "tick 7, fallbacking to market value")
            self.assertRegex(stdout_mock.getvalue(), "tick 7, market value, cancelling and adjusting to")

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []

        for i in [10, 13, 16]:
            with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_called()
                new_order_mock.run.assert_called()
                prepare_order.assert_called_with(compute_value="default")
                self.assertNotRegex(stdout_mock.getvalue(), "tick {}, fallbacking to market value".format(i))
                self.assertRegex(stdout_mock.getvalue(), "tick {}, market value, cancelling and adjusting to".format(i))

            order_mock.reset_mock()
            new_order_mock.reset_mock()
            trade.orders = []

        for i in [8, 9, 11, 12]:
            with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                self.assertEqual("", stdout_mock.getvalue())

            order_mock.reset_mock()
            new_order_mock.reset_mock()
            trade.orders = []


    @mock.patch('sys.stdout', new_callable=StringIO)
    def test_print_with_order(self, mock_stdout):
        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        order_mock1 = mock.Mock()
        order_mock1.__repr__ = mock.Mock()
        order_mock1.__repr__.return_value = "Mock 1"
        order_mock2 = mock.Mock()
        order_mock2.__repr__ = mock.Mock()
        order_mock2.__repr__.return_value = "Mock 2"
        trade.orders.append(order_mock1)
        trade.orders.append(order_mock2)

        trade.print_with_order()

        out = mock_stdout.getvalue().split("\n")
        self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0])
        self.assertEqual("\tMock 1", out[1])
        self.assertEqual("\tMock 2", out[2])

    def test__repr(self):
        value_from = portfolio.Amount("BTC", "0.5")
        value_from.linked_to = portfolio.Amount("ETH", "10.0")
        value_to = portfolio.Amount("BTC", "1.0")
        trade = portfolio.Trade(value_from, value_to, "ETH")

        self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))

@unittest.skipUnless("unit" in limits, "Unit skipped")
class OrderTest(WebMockTestCase):
    def test_values(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertEqual("buy", order.action)
        self.assertEqual(10, order.amount.value)
        self.assertEqual("ETH", order.amount.currency)
        self.assertEqual(D("0.1"), order.rate)
        self.assertEqual("BTC", order.base_currency)
        self.assertEqual("market", order.market)
        self.assertEqual("long", order.trade_type)
        self.assertEqual("pending", order.status)
        self.assertEqual("trade", order.trade)
        self.assertIsNone(order.id)
        self.assertFalse(order.close_if_possible)

    def test__repr(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending])", repr(order))

        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade",
                close_if_possible=True)
        self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order))

    def test_account(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertEqual("exchange", order.account)

        order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "short", "market", "trade")
        self.assertEqual("margin", order.account)

    def test_pending(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertTrue(order.pending)
        order.status = "open"
        self.assertFalse(order.pending)

    def test_open(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertFalse(order.open)
        order.status = "open"
        self.assertTrue(order.open)

    def test_finished(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        self.assertFalse(order.finished)
        order.status = "closed"
        self.assertTrue(order.finished)
        order.status = "canceled"
        self.assertTrue(order.finished)
        order.status = "error"
        self.assertTrue(order.finished)

    @mock.patch.object(portfolio.Order, "fetch")
    def test_cancel(self, fetch):
        market = mock.Mock()
        portfolio.TradeStore.debug = True
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", market, "trade")
        order.status = "open"

        order.cancel()
        market.cancel_order.assert_not_called()
        self.assertEqual("canceled", order.status)

        portfolio.TradeStore.debug = False
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", market, "trade")
        order.status = "open"
        order.id = 42

        order.cancel()
        market.cancel_order.assert_called_with(42)
        fetch.assert_called_once()

    def test_dust_amount_remaining(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
        self.assertFalse(order.dust_amount_remaining())

        order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001")))
        self.assertTrue(order.dust_amount_remaining())

    @mock.patch.object(portfolio.Order, "fetch")
    @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
    def test_remaining_amount(self, filled_amount, fetch):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")

        self.assertEqual(9, order.remaining_amount().value)
        order.fetch.assert_not_called()

        order.status = "open"
        self.assertEqual(9, order.remaining_amount().value)
        fetch.assert_called_once()

    @mock.patch.object(portfolio.Order, "fetch")
    def test_filled_amount(self, fetch):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
            "id": 42, "type": "buy", "fee": "0.0015",
            "date": "2017-12-30 12:00:12", "rate": "0.1",
            "amount": "3", "total": "0.3"
            }))
        order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
            "id": 43, "type": "buy", "fee": "0.0015",
            "date": "2017-12-30 13:00:12", "rate": "0.2",
            "amount": "2", "total": "0.4"
            }))
        self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount())
        fetch.assert_not_called()
        order.status = "open"
        self.assertEqual(portfolio.Amount("ETH", 5), order.filled_amount(in_base_currency=False))
        fetch.assert_called_once()
        self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))

    def test_fetch_mouvements(self):
        market = mock.Mock()
        market.privatePostReturnOrderTrades.return_value = [
                {
                    "id": 42, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 12:00:12", "rate": "0.1",
                    "amount": "3", "total": "0.3"
                    },
                {
                    "id": 43, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 13:00:12", "rate": "0.2",
                    "amount": "2", "total": "0.4"
                    }
            ]
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", market, "trade")
        order.id = 12
        order.mouvements = ["Foo", "Bar", "Baz"]

        order.fetch_mouvements()

        market.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
        self.assertEqual(2, len(order.mouvements))
        self.assertEqual(42, order.mouvements[0].id)
        self.assertEqual(43, order.mouvements[1].id)

    def test_mark_finished_order(self):
        market = mock.Mock()
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "short", market, "trade",
                close_if_possible=True)
        order.status = "closed"
        portfolio.TradeStore.debug = False

        order.mark_finished_order()
        market.close_margin_position.assert_called_with("ETH", "BTC")
        market.close_margin_position.reset_mock()

        order.status = "open"
        order.mark_finished_order()
        market.close_margin_position.assert_not_called()

        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "short", market, "trade",
                close_if_possible=False)
        order.status = "closed"
        order.mark_finished_order()
        market.close_margin_position.assert_not_called()

        order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "short", market, "trade",
                close_if_possible=True)
        order.status = "closed"
        order.mark_finished_order()
        market.close_margin_position.assert_not_called()

        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", market, "trade",
                close_if_possible=True)
        order.status = "closed"
        order.mark_finished_order()
        market.close_margin_position.assert_not_called()

        portfolio.TradeStore.debug = True

        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "short", market, "trade",
                close_if_possible=True)
        order.status = "closed"

        order.mark_finished_order()
        market.close_margin_position.assert_not_called()

    @mock.patch.object(portfolio.Order, "fetch_mouvements")
    def test_fetch(self, fetch_mouvements):
        time = self.time.time()
        with mock.patch.object(portfolio.time, "time") as time_mock:
            market = mock.Mock()
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", market, "trade")
            order.id = 45
            with self.subTest(debug=True):
                portfolio.TradeStore.debug = True
                order.fetch()
                time_mock.assert_not_called()
                order.fetch(force=True)
                time_mock.assert_not_called()
                market.fetch_order.assert_not_called()
                fetch_mouvements.assert_not_called()
            self.assertIsNone(order.fetch_cache_timestamp)

            with self.subTest(debug=False):
                portfolio.TradeStore.debug = False
                time_mock.return_value = time
                market.fetch_order.return_value = {
                        "status": "foo",
                        "datetime": "timestamp"
                        }
                order.fetch()

                market.fetch_order.assert_called_once()
                fetch_mouvements.assert_called_once()
                self.assertEqual("foo", order.status)
                self.assertEqual("timestamp", order.timestamp)
                self.assertEqual(time, order.fetch_cache_timestamp)
                self.assertEqual(1, len(order.results))

                market.fetch_order.reset_mock()
                fetch_mouvements.reset_mock()

                time_mock.return_value = time + 8
                order.fetch()
                market.fetch_order.assert_not_called()
                fetch_mouvements.assert_not_called()

                order.fetch(force=True)
                market.fetch_order.assert_called_once()
                fetch_mouvements.assert_called_once()

                market.fetch_order.reset_mock()
                fetch_mouvements.reset_mock()

                time_mock.return_value = time + 19
                order.fetch()
                market.fetch_order.assert_called_once()
                fetch_mouvements.assert_called_once()

    @mock.patch.object(portfolio.Order, "fetch")
    @mock.patch.object(portfolio.Order, "mark_finished_order")
    def test_get_status(self, mark_finished_order, fetch):
        with self.subTest(debug=True):
            portfolio.TradeStore.debug = True
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", "market", "trade")
            self.assertEqual("pending", order.get_status())
            fetch.assert_not_called()

        with self.subTest(debug=False, finished=False):
            portfolio.TradeStore.debug = False
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", "market", "trade")
            def _fetch(order):
                def update_status():
                    order.status = "open"
                return update_status
            fetch.side_effect = _fetch(order)
            self.assertEqual("open", order.get_status())
            mark_finished_order.assert_not_called()
            fetch.assert_called_once()

        mark_finished_order.reset_mock()
        fetch.reset_mock()
        with self.subTest(debug=False, finished=True):
            portfolio.TradeStore.debug = False
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", "market", "trade")
            def _fetch(order):
                def update_status():
                    order.status = "closed"
                return update_status
            fetch.side_effect = _fetch(order)
            self.assertEqual("closed", order.get_status())
            mark_finished_order.assert_called_once()
            fetch.assert_called_once()

    def test_run(self):
        market = mock.Mock()

        market.order_precision.return_value = 4
        with self.subTest(debug=True),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            portfolio.TradeStore.debug = True
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", market, "trade")
            order.run()
            market.create_order.assert_not_called()
            self.assertEqual("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)\n", stdout_mock.getvalue())
            self.assertEqual("open", order.status)
            self.assertEqual(1, len(order.results))
            self.assertEqual(-1, order.id)

        market.create_order.reset_mock()
        with self.subTest(debug=False):
            portfolio.TradeStore.debug = False
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", market, "trade")
            market.create_order.return_value = { "id": 123 }
            order.run()
            market.create_order.assert_called_once()
            self.assertEqual(1, len(order.results))
            self.assertEqual("open", order.status)

        market.create_order.reset_mock()
        with self.subTest(exception=True),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", market, "trade")
            market.create_order.side_effect = Exception("bouh")
            order.run()
            market.create_order.assert_called_once()
            self.assertEqual(0, len(order.results))
            self.assertEqual("error", order.status)
            self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order")
            self.assertRegex(stdout_mock.getvalue(), "Exception: bouh")

@unittest.skipUnless("unit" in limits, "Unit skipped")
class MouvementTest(WebMockTestCase):
    def test_values(self):
        mouvement = portfolio.Mouvement("ETH", "BTC", {
            "id": 42, "type": "buy", "fee": "0.0015",
            "date": "2017-12-30 12:00:12", "rate": "0.1",
            "amount": "10", "total": "1"
            })
        self.assertEqual("ETH", mouvement.currency)
        self.assertEqual("BTC", mouvement.base_currency)
        self.assertEqual(42, mouvement.id)
        self.assertEqual("buy", mouvement.action)
        self.assertEqual(D("0.0015"), mouvement.fee_rate)
        self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), mouvement.date)
        self.assertEqual(D("0.1"), mouvement.rate)
        self.assertEqual(portfolio.Amount("ETH", "10"), mouvement.total)
        self.assertEqual(portfolio.Amount("BTC", "1"), mouvement.total_in_base)

@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
class AcceptanceTest(WebMockTestCase):
    @unittest.expectedFailure
    def test_success_sell_only_necessary(self):
        fetch_balance = {
                "ETH": {
                    "exchange_free": D("1.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("1.0"),
                    "total": D("1.0"),
                    },
                "ETC": {
                    "exchange_free": D("4.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("4.0"),
                    "total": D("4.0"),
                    },
                "XVG": {
                    "exchange_free": D("1000.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("1000.0"),
                    "total": D("1000.0"),
                    },
                }
        repartition = {
                "ETH":  (D("0.25"), "long"),
                "ETC":  (D("0.25"), "long"),
                "BTC":  (D("0.4"),  "long"),
                "BTD":  (D("0.01"), "short"),
                "B2X":  (D("0.04"), "long"),
                "USDT": (D("0.05"), "long"),
                }

        def fetch_ticker(symbol):
            if symbol == "ETH/BTC":
                return {
                        "symbol": "ETH/BTC",
                        "bid": D("0.14"),
                        "ask": D("0.16")
                        }
            if symbol == "ETC/BTC":
                return {
                        "symbol": "ETC/BTC",
                        "bid": D("0.002"),
                        "ask": D("0.003")
                        }
            if symbol == "XVG/BTC":
                return {
                        "symbol": "XVG/BTC",
                        "bid": D("0.00003"),
                        "ask": D("0.00005")
                        }
            if symbol == "BTD/BTC":
                return {
                        "symbol": "BTD/BTC",
                        "bid": D("0.0008"),
                        "ask": D("0.0012")
                        }
            if symbol == "B2X/BTC":
                return {
                        "symbol": "B2X/BTC",
                        "bid": D("0.0008"),
                        "ask": D("0.0012")
                        }
            if symbol == "USDT/BTC":
                raise helper.ExchangeError
            if symbol == "BTC/USDT":
                return {
                        "symbol": "BTC/USDT",
                        "bid": D("14000"),
                        "ask": D("16000")
                        }
            self.fail("Shouldn't have been called with {}".format(symbol))

        market = mock.Mock()
        market.fetch_all_balances.return_value = fetch_balance
        market.fetch_ticker.side_effect = fetch_ticker
        with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
            # Action 1
            helper.prepare_trades(market)

        balances = portfolio.BalanceStore.all
        self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
        self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
        self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)


        trades = portfolio.TradeStore.all
        self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
        self.assertEqual("dispose", trades[0].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
        self.assertEqual("acquire", trades[1].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
        self.assertEqual("dispose", trades[2].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
        self.assertEqual("acquire", trades[3].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
        self.assertEqual("acquire", trades[4].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
        self.assertEqual("acquire", trades[5].action)

        # Action 2
        portfolio.TradeStore.prepare_orders(only="dispose", compute_value=lambda x, y: x["bid"] * D("1.001"))

        all_orders = portfolio.TradeStore.all_orders(state="pending")
        self.assertEqual(2, len(all_orders))
        self.assertEqual(2, 3*all_orders[0].amount.value)
        self.assertEqual(D("0.14014"), all_orders[0].rate)
        self.assertEqual(1000, all_orders[1].amount.value)
        self.assertEqual(D("0.00003003"), all_orders[1].rate)


        def create_order(symbol, type, action, amount, price=None, account="exchange"):
            self.assertEqual("limit", type)
            if symbol == "ETH/BTC":
                self.assertEqual("sell", action)
                self.assertEqual(D('0.66666666'), amount)
                self.assertEqual(D("0.14014"), price)
            elif symbol == "XVG/BTC":
                self.assertEqual("sell", action)
                self.assertEqual(1000, amount)
                self.assertEqual(D("0.00003003"), price)
            else:
                self.fail("I shouldn't have been called")

            return {
                    "id": symbol,
                    }
        market.create_order.side_effect = create_order
        market.order_precision.return_value = 8

        # Action 3
        portfolio.TradeStore.run_orders()

        self.assertEqual("open", all_orders[0].status)
        self.assertEqual("open", all_orders[1].status)

        market.fetch_order.return_value = { "status": "closed", "datetime": "2018-01-20 13:40:00" }
        market.privatePostReturnOrderTrades.return_value = [
                {
                    "id": 42, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 12:00:12", "rate": "0.1",
                    "amount": "10", "total": "1"
                    }
                ]
        with mock.patch.object(portfolio.time, "sleep") as sleep:
            # Action 4
            helper.follow_orders(verbose=False)

            sleep.assert_called_with(30)

        for order in all_orders:
            self.assertEqual("closed", order.status)

        fetch_balance = {
                "ETH": {
                    "exchange_free": D("1.0") / 3,
                    "exchange_used": D("0.0"),
                    "exchange_total": D("1.0") / 3,
                    "margin_total": 0,
                    "total": D("1.0") / 3,
                    },
                "BTC": {
                    "exchange_free": D("0.134"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("0.134"),
                    "margin_total": 0,
                    "total": D("0.134"),
                    },
                "ETC": {
                    "exchange_free": D("4.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("4.0"),
                    "margin_total": 0,
                    "total": D("4.0"),
                    },
                "XVG": {
                    "exchange_free": D("0.0"),
                    "exchange_used": D("0.0"),
                    "exchange_total": D("0.0"),
                    "margin_total": 0,
                    "total": D("0.0"),
                    },
                }
        market.fetch_all_balances.return_value = fetch_balance

        with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
            # Action 5
            helper.update_trades(market, only="acquire", compute_value="average")

        balances = portfolio.BalanceStore.all
        self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
        self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
        self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
        self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)


        trades = portfolio.TradeStore.all
        self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
        self.assertEqual("dispose", trades[0].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[1].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[1].value_to)
        self.assertEqual("acquire", trades[1].action)

        self.assertNotIn("BTC", trades)

        self.assertEqual(portfolio.Amount("BTC", D("0.04")), trades[2].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[2].value_to)
        self.assertEqual("dispose", trades[2].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[3].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("-0.002")), trades[3].value_to)
        self.assertEqual("acquire", trades[3].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[4].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades[4].value_to)
        self.assertEqual("acquire", trades[4].action)

        self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades[5].value_from)
        self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades[5].value_to)
        self.assertEqual("acquire", trades[5].action)

        # Action 6
        portfolio.TradeStore.prepare_orders(only="acquire", compute_value=lambda x, y: x["ask"])

        all_orders = portfolio.TradeStore.all_orders(state="pending")
        self.assertEqual(4, len(all_orders))
        self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount))
        self.assertEqual(D("0.003"), all_orders[0].rate)
        self.assertEqual("buy", all_orders[0].action)
        self.assertEqual("long", all_orders[0].trade_type)

        self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount))
        self.assertEqual(D("0.0012"), all_orders[1].rate)
        self.assertEqual("sell", all_orders[1].action)
        self.assertEqual("short", all_orders[1].trade_type)

        diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount
        self.assertAlmostEqual(0, diff.value)
        self.assertEqual(D("0.0012"), all_orders[2].rate)
        self.assertEqual("buy", all_orders[2].action)
        self.assertEqual("long", all_orders[2].trade_type)

        self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount)
        self.assertEqual(D("16000"), all_orders[3].rate)
        self.assertEqual("sell", all_orders[3].action)
        self.assertEqual("long", all_orders[3].trade_type)

        # Action 6b
        # TODO:
        # Move balances to margin

        # Action 7
        # TODO
        # portfolio.TradeStore.run_orders()

        with mock.patch.object(portfolio.time, "sleep") as sleep:
            # Action 8
            helper.follow_orders(verbose=False)

            sleep.assert_called_with(30)

if __name__ == '__main__':
    unittest.main()