aboutsummaryrefslogblamecommitdiff
path: root/test.py
blob: 34b2fe3c228cf46b70878ea998965270eb273109 (plain) (tree)
1
2
3
4
5
6
7
8
9
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
          

                
                                
                         

                    
                       
             
 









                                                     








                                            

                                                          




                                                           
                                                                                                    
                                                          




                                                                     



                                     





                                               
                                                      












                                                                     

                                                     









                                                                


                                                          



                                                   



                                                          




                                                         


                                                                             
 

                                                       
















                                                               

                                                                 













                                                  

                                                                                        
 



                                                                    

                                                    
                                                         


                                               

                                                               
                                                          
 

                                             

























                                                                                              






                                                     

                                                                     


































                                                                     
                                                      
                                  
                          

                                                 

                                                





                                                                 
                                                                      
                                           


                                                                         
                                                                      
                                        

                                    
                                        



                                                              
                                                              



                                                                   





                                                                                                     


                                                                          




                                                                      









                                                    

                                                 



                                                             
                                                





                                                    

                                              
                         
                                                





                                            

                                                 
 
                                                             

                                                             
                                                





                                                    






                                                                  


                                            

                                                              






                                            

                                                              



                                            

                                                             
 


                                          
                            

                                            

                                                             












                                               



































                                               
















                                               





















                                                                



















                                                                                                                           










                                                                                      
                                                      
                                   
                          


























                                                                           
 

                                                 


                                                                               





                                                                                                                      
 


                                                                                      


                                                                                                                              
 


                                                                                   




                                                                                                                  
 



                                                                                                                                           











                                                                                       
                                                      









                                            
 





                                                         
 




                                                        
 

                                                        
 






                                              
 











                                                               
 



                                                                        
 











                                                          
 






                                                          
 
                                                          

                                                              


                                                                                                         
                                    

                                           
                 






                                                   
                                                                    

                                            
                            
                                                  
                         


                                                   
                                         

                        


                                                   
                                         

                      

                                                                
                                     




                                                     


                                                              
                                                                          
                                                                            
 
                                                          

                                                              


                                                                                                        




























                                                                    

                                                                
                                    







                                                              
                                                                         
                                                                            

                                                          

                                                              


                                                                                                                     






















                                                                    

                                                                
                                                 







                                                            
                                                                                      
                                                                            
 


                                                          




                                                                     
























                                                                               
                                                 












                                                                              





















                                                                       


                                 
                                                                                                        





                                                        



                                                                
                                                                     

















                                                                              

                                                                                                 





                                                         

                                                                   
                         

                                                                                 


                                                                                             
                                                                                            


                                                              
                                                                
                                                          
                                                                                           












                                                 
 
                                   
                                                                     
                                                                      
                                                    
                                                                  
                                                    
 

                                                                

                                                                          


















                                                       



                                                       
 


                                                              
                                                          
                                                                
                                                          


                                                                      













                                                  



                                                                       
                                                                      
                                                

                                                                  

                                     
                                                                       




                                                              
                                                          
                                                                
                                                          


                                                                        













                                                 



                                                                      
                                                                     
                                                                

                                                                  


                                                             
                                                                      



                                                              
                                                          
                                                                
                                                          


                                                                      













                                                   



                                                                        


                                                                                  

                                     
                                                                        




                                                              
                                                          
                                                                
                                                          


                                                                      













                                                  



                                                                       
                                                                      

                                                                  


                                                             
                                                                       
 
 
                                                      
                                      
                                                            


                                                                             













                                                                      







                                                   



                                                              



                                                                 
 

                                                        



                                                   

                                                         

                                     
                                                        



                                                  
                                         

                                     
                                                        



                                                      
                                         

                                     
                                                        



                                                      
                                          
 

                                                           

                                 
 


                                                  





                                                                             


                                                                   



                                                                         
                                                               


                                              
                               





                                                                             
                                                                 












                                                                          

                                                           
                                                          
                                                                 










                                                                         



                                                                





































                                                                         
 
                                                      
































                                               

                                                        











                                                 
                            




                                    
 



                                                                   


                                                                   
 


                                                                                        


                                                                   
 


                                                                                                              


                                                                   
 

                                                             

                                                                   
 


                                                                                               
 


                                                                      
                                                                

                                                                                                      
                                                          
 
                                                          


                                                                            





                                                                    
                            

                                           
                                             
                 
                                                   
 
                                                                                         
                                                          


                                                                 
                                                          
                                                         

                                                                    















                                                                                       
















                                                  
                                                      























                                                                                 
                                                      
                                 
 
                                    



                                                              










                                                        
 


                                                            
 
                          



                                                              
 



                                                             
                                                 















                                                              
                                                 
 
                                





































                                                              
                                                                          

                            
                                                                           


                                   


                                                                                
 





























                                                                                                      

                                                                    













                                                                                                      
                                                                                        












































































































                                                                                                              
                                                   

                                 

                                                                                       


                                                      

                                                                             




                                       
                                                                                



                                              








                                                                               




                                   
                                                                                



                                              








                                                                                 




                                   
                                                                                



                                                                     








                                                                       





                                   
                                                                                                          



                                                                         








                                                                           





                                       
                                                                                                          


                                                      

                                                                                  



                                       
 
 

                                                          










                                                              









                                                             




                                        






                                                                                                                          
 
                         





                                                                                                                 
 












                                                              


























                                                                                                 




















                                                                   


































                                                                    

                                               







                                                                   

                                                          










                                                                   
                                                         



























                                                                                                 
                                                          



                                                                   
                                                          













                                                                                                    
                                                                  



                                                                 
                                                                  















                                                                                   





                                                                                 

                                                     












































                                                                     
                                                          

                                                           

                                                         









                                                                       

                                                                  



                                                      

                                                                  




































                                                                   
                                                                 


                                                              

                                                                        





                                                                       
                                                              

































                                                                       
                                                                    




                                                                       
                                                                                                                                                    
















                                                                       
                                                                    






                                                                       
                                                       
 












                                                                                                 



                                                       
                                                          












                                                                                     








                                                                             






                                                                                                                               
 






                                                                                                      








































































                                                                                                              
                                                               




                                    




                                         




















































































































































































































































































































                                                                                                                                      
                                                                  

                                      
                                               

                                                   

                         


                                               


                                      


                                               


                                      


                                                  



                                         





                                             


























                                            





                                            
                                    
                                          








                                                                          
                                                              
                                                      
                                                                                             
                      
                                         
 
                                             




                                                                              
                                         


                                                                                  
 


                                                                                  
 


                                                                                  
 

                                                                                  
                                                     
 


                                                                                  
 


                                                                                  

                  
                                                                                                             
 
                                                                     






                                                             
                                                                                       

                                           
                                                
                                                         

                                                     
                                                








                                                         
                                               

                  
                                         



                                                      


                                                                                                   
                                                                  



                                                                 

                                                                 
                                               







                                                    



                                                   


                                          



                                                 


                                        



                                               


                                      



                                               


                                      
                                                              
 
                                                                                             
                      
                                                                                 
 
                                             





                                                                                    



                                                                                  
 


                                                                                  


                                       


                                                                                  
 


                                                                                  
 


                                                                                  
 


                                                                                  

                  
                                                                                                
 
                                                                     

                                                                                                

                                                        
                                                          
 
                                                                                               
                                                         












                                                                                    
 



                                 

                  
                                           

                                                                 
                      
                                               


                                        

                          
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.ReportStore,
                    logs=[], verbose_print=True),
                mock.patch.multiple(portfolio.BalanceStore,
                    all={},),
                mock.patch.multiple(portfolio.TradeStore,
                    all=[],
                    debug=False),
                mock.patch.multiple(portfolio.Portfolio, last_date=None, 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)

    @mock.patch("portfolio.ReportStore")
    def test_get_cryptoportfolio(self, report_store):
        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)
        report_store.log_error.assert_not_called()
        report_store.log_http_request.assert_called_once()
        report_store.log_http_request.reset_mock()

        portfolio.Portfolio.get_cryptoportfolio()
        self.assertIsNone(portfolio.Portfolio.data)
        self.assertEqual(2, self.wm.call_count)
        report_store.log_error.assert_not_called()
        report_store.log_http_request.assert_called_once()
        report_store.log_http_request.reset_mock()


        portfolio.Portfolio.data = "Foo"
        portfolio.Portfolio.get_cryptoportfolio()
        self.assertEqual("Foo", portfolio.Portfolio.data)
        self.assertEqual(3, self.wm.call_count)
        report_store.log_error.assert_called_once_with("get_cryptoportfolio",
                exception=mock.ANY)
        report_store.log_http_request.assert_not_called()

    @mock.patch("portfolio.ReportStore")
    def test_parse_cryptoportfolio(self, report_store):
        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"),
                }
        date = portfolio.datetime(2018, 1, 8)
        self.assertDictEqual(expected, liquidities["high"][date])

        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"][date])
        self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)

        report_store.log_http_request.assert_called_once_with("GET",
                portfolio.Portfolio.URL, None, mock.ANY, mock.ANY)
        report_store.log_http_request.reset_mock()

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

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

        portfolio.Portfolio.parse_cryptoportfolio(refetch=True)
        self.assertEqual(2, self.wm.call_count)
        report_store.log_http_request.assert_called_once()

    @mock.patch("portfolio.ReportStore")
    def test_repartition(self, report_store):
        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"))

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

        portfolio.Portfolio.repartition()
        self.assertEqual(1, self.wm.call_count)

        portfolio.Portfolio.repartition(refetch=True)
        self.assertEqual(2, self.wm.call_count)
        report_store.log_http_request.assert_called()
        self.assertEqual(2, report_store.log_http_request.call_count)

    @mock.patch.object(portfolio.time, "sleep")
    @mock.patch.object(portfolio.Portfolio, "repartition")
    def test_wait_for_recent(self, repartition, sleep):
        self.call_count = 0
        def _repartition(refetch):
            self.assertTrue(refetch)
            self.call_count += 1
            portfolio.Portfolio.last_date = portfolio.datetime.now()\
                - portfolio.timedelta(10)\
                + portfolio.timedelta(self.call_count)
        repartition.side_effect = _repartition

        portfolio.Portfolio.wait_for_recent()
        sleep.assert_called_with(30)
        self.assertEqual(6, sleep.call_count)
        self.assertEqual(7, repartition.call_count)

        sleep.reset_mock()
        repartition.reset_mock()
        portfolio.Portfolio.last_date = None
        self.call_count = 0
        portfolio.Portfolio.wait_for_recent(delta=15)
        sleep.assert_not_called()
        self.assertEqual(1, repartition.call_count)

        sleep.reset_mock()
        repartition.reset_mock()
        portfolio.Portfolio.last_date = None
        self.call_count = 0
        portfolio.Portfolio.wait_for_recent(delta=1)
        sleep.assert_called_with(30)
        self.assertEqual(9, sleep.call_count)
        self.assertEqual(10, repartition.call_count)

@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)

        self.assertEqual(amount1, amount1 + 0)

    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__rsub(self):
        amount = portfolio.Amount("ETH", "1.6")
        with self.assertRaises(Exception):
            3 - amount

        self.assertEqual(portfolio.Amount("ETH", "-1.6"), -amount)

    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))

    def test_as_json(self):
        amount = portfolio.Amount("BTX", 32)
        self.assertEqual({"currency": "BTX", "value": D("32")}, amount.as_json())

        amount = portfolio.Amount("BTX", "1E-10")
        self.assertEqual({"currency": "BTX", "value": D("0")}, amount.as_json())

        amount = portfolio.Amount("BTX", "1E-5")
        self.assertEqual({"currency": "BTX", "value": D("0.00001")}, amount.as_json())
        self.assertEqual("0.00001", str(amount.as_json()["value"]))

@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))

    def test_as_json(self):
        balance = portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })
        as_json = balance.as_json()
        self.assertEqual(set(portfolio.Balance.base_keys), set(as_json.keys()))
        self.assertEqual(D(0), as_json["total"])
        self.assertEqual(D(2), as_json["exchange_total"])
        self.assertEqual(D(2), as_json["exchange_free"])
        self.assertEqual(D(0), as_json["exchange_used"])
        self.assertEqual(D(0), as_json["margin_total"])
        self.assertEqual(D(0), as_json["margin_free"])
        self.assertEqual(D(0), as_json["margin_borrowed"])

@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")
    @mock.patch("store.ReportStore")
    @mock.patch("helper.ReportStore")
    def test_prepare_trades(self, report_store_h, report_store, 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")
                    },
                }
        portfolio.BalanceStore.fetch_balances(market, tag="tag")

        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)
        report_store_h.log_stage.assert_called_once_with("prepare_trades")
        report_store.log_balances.assert_called_once_with(market, tag="tag")

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.TradeStore, "compute_trades")
    @mock.patch("store.ReportStore")
    @mock.patch("helper.ReportStore")
    def test_update_trades(self, report_store_h, report_store, 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")
                    },
                }
        portfolio.BalanceStore.fetch_balances(market, tag="tag")

        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)
        report_store_h.log_stage.assert_called_once_with("update_trades")
        report_store.log_balances.assert_called_once_with(market, tag="tag")

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(helper, "get_ticker")
    @mock.patch.object(portfolio.TradeStore, "compute_trades")
    @mock.patch("store.ReportStore")
    @mock.patch("helper.ReportStore")
    def test_prepare_trades_to_sell_all(self, report_store_h, report_store, 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")
                    },
                }
        portfolio.BalanceStore.fetch_balances(market, tag="tag")

        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)
        report_store_h.log_stage.assert_called_once_with("prepare_trades_to_sell_all")
        report_store.log_balances.assert_called_once_with(market, tag="tag")

    @mock.patch.object(portfolio.time, "sleep")
    @mock.patch.object(portfolio.TradeStore, "all_orders")
    def test_follow_orders(self, all_orders, time_mock):
        for debug, sleep in [
                (False, None), (True, None),
                (False, 12), (True, 12)]:
            with self.subTest(sleep=sleep, debug=debug), \
                    mock.patch("helper.ReportStore") as report_store:
                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(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)
                report_store.log_stage.assert_called()
                calls = [
                        mock.call("follow_orders_begin"),
                        mock.call("follow_orders_tick_1"),
                        mock.call("follow_orders_tick_2"),
                        mock.call("follow_orders_tick_3"),
                        mock.call("follow_orders_end"),
                        ]
                report_store.log_stage.assert_has_calls(calls)
                report_store.log_orders.assert_called()
                self.assertEqual(3, report_store.log_orders.call_count)
                calls = [
                        mock.call([order_mock1, order_mock2], tick=1),
                        mock.call([order_mock1, order_mock3], tick=2),
                        mock.call([order_mock1, order_mock3], tick=3),
                        ]
                report_store.log_orders.assert_has_calls(calls)
                calls = [
                        mock.call(order_mock1, 3, finished=True),
                        mock.call(order_mock3, 3, finished=True),
                        ]
                report_store.log_order.assert_has_calls(calls)

                if sleep is None:
                    if debug:
                        report_store.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
                        time_mock.assert_called_with(7)
                    else:
                        time_mock.assert_called_with(30)
                else:
                    time_mock.assert_called_with(sleep)

    @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("helper.ReportStore") as report_store:
                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" })
                balance3 = portfolio.Balance("ETC", { "margin_free": "10" })
                portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}

                market = mock.Mock()

                helper.move_balances(market, debug=debug)

                fetch_balances.assert_called_with(market)
                report_store.log_move_balances.assert_called_once()

                if debug:
                    report_store.log_debug_action.assert_called()
                    self.assertEqual(3, report_store.log_debug_action.call_count)
                else:
                    market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
                    market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
                    market.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange")

    @mock.patch.object(helper, "prepare_trades")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.ReportStore, "log_stage")
    def test_print_orders(self, log_stage, fetch_balances, 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)
        fetch_balances.assert_called_with(market, tag="print_orders")
        prepare_trades.assert_called_with(market, base_currency="BTC",
                compute_value="average", debug=True)
        prepare_orders.assert_called_with(compute_value="average")
        log_stage.assert_called_with("print_orders")

    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.BalanceStore, "in_currency")
    @mock.patch.object(helper.ReportStore, "print_log")
    def test_print_balances(self, print_log, in_currency, fetch_balances):
        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}),
                }
        in_currency.return_value = {
                "BTC": portfolio.Amount("BTC", "0.65"),
                "ETH": portfolio.Amount("BTC", "0.3"),
        }
        helper.print_balances(market)
        fetch_balances.assert_called_with(market)
        print_log.assert_has_calls([
            mock.call("total:"),
            mock.call(portfolio.Amount("BTC", "0.95")),
            ])

    @mock.patch.object(helper, "prepare_trades")
    @mock.patch.object(helper, "follow_orders")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.TradeStore, "run_orders")
    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.ReportStore, "log_stage")
    def test_process_sell_needed__1_sell(self, log_stage,
            fetch_balances, run_orders, prepare_orders, follow_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.process_sell_needed__1_sell(market)
        fetch_balances.assert_has_calls([
            mock.call(market, tag="process_sell_needed__1_sell_begin"),
            mock.call(market, tag="process_sell_needed__1_sell_end"),
            ])
        prepare_trades.assert_called_with(market, base_currency="BTC",
                liquidity="medium", debug=False)
        prepare_orders.assert_called_with(compute_value="average",
                only="dispose")
        run_orders.assert_called()
        follow_orders.assert_called()
        log_stage.assert_called_with("process_sell_needed__1_sell_end")

    @mock.patch.object(helper, "update_trades")
    @mock.patch.object(helper, "follow_orders")
    @mock.patch.object(helper, "move_balances")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.TradeStore, "run_orders")
    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.ReportStore, "log_stage")
    def test_process_sell_needed__2_buy(self, log_stage, fetch_balances,
            run_orders, prepare_orders, move_balances, follow_orders,
            update_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.process_sell_needed__2_buy(market)
        fetch_balances.assert_has_calls([
            mock.call(market, tag="process_sell_needed__2_buy_begin"),
            mock.call(market, tag="process_sell_needed__2_buy_end"),
            ])
        update_trades.assert_called_with(market, base_currency="BTC",
                debug=False, liquidity="medium", only="acquire")
        prepare_orders.assert_called_with(compute_value="average",
                only="acquire")
        move_balances.assert_called_with(market, debug=False)
        run_orders.assert_called()
        follow_orders.assert_called()
        log_stage.assert_called_with("process_sell_needed__2_buy_end")

    @mock.patch.object(helper, "prepare_trades_to_sell_all")
    @mock.patch.object(helper, "follow_orders")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.TradeStore, "run_orders")
    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.ReportStore, "log_stage")
    def test_process_sell_all__1_sell(self, log_stage, fetch_balances,
            run_orders, prepare_orders, follow_orders,
            prepare_trades_to_sell_all):
        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.process_sell_all__1_all_sell(market)
        fetch_balances.assert_has_calls([
            mock.call(market, tag="process_sell_all__1_all_sell_begin"),
            mock.call(market, tag="process_sell_all__1_all_sell_end"),
            ])
        prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
                debug=False)
        prepare_orders.assert_called_with(compute_value="average")
        run_orders.assert_called()
        follow_orders.assert_called()
        log_stage.assert_called_with("process_sell_all__1_all_sell_end")

    @mock.patch.object(helper, "prepare_trades")
    @mock.patch.object(helper, "follow_orders")
    @mock.patch.object(helper, "move_balances")
    @mock.patch.object(portfolio.TradeStore, "prepare_orders")
    @mock.patch.object(portfolio.TradeStore, "run_orders")
    @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
    @mock.patch.object(portfolio.ReportStore, "log_stage")
    def test_process_sell_all__2_all_buy(self, log_stage,
            fetch_balances, run_orders, prepare_orders, move_balances,
            follow_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.process_sell_all__2_all_buy(market)
        fetch_balances.assert_has_calls([
            mock.call(market, tag="process_sell_all__2_all_buy_begin"),
            mock.call(market, tag="process_sell_all__2_all_buy_end"),
            ])
        prepare_trades.assert_called_with(market, base_currency="BTC",
                liquidity="medium", debug=False)
        prepare_orders.assert_called_with(compute_value="average")
        move_balances.assert_called_with(market, debug=False)
        run_orders.assert_called()
        follow_orders.assert_called()
        log_stage.assert_called_with("process_sell_all__2_all_buy_end")


@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeStoreTest(WebMockTestCase):
    @mock.patch.object(portfolio.BalanceStore, "currencies")
    @mock.patch.object(portfolio.TradeStore, "trade_if_matching")
    @mock.patch.object(portfolio.ReportStore, "log_trades")
    def test_compute_trades(self, log_trades, 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")),
                }
        side_effect = [
                (True, 1),
                (False, 2),
                (False, 3),
                (True, 4),
                (True, 5)
                ]
        trade_if_matching.side_effect = side_effect

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

        self.assertEqual(5, trade_if_matching.call_count)
        self.assertEqual(3, len(portfolio.TradeStore.all))
        self.assertEqual([1, 4, 5], portfolio.TradeStore.all)
        log_trades.assert_called_with(side_effect, "only", False)

    def test_trade_if_matching(self):
        result = portfolio.TradeStore.trade_if_matching(
                portfolio.Amount("BTC", D("0")),
                portfolio.Amount("BTC", D("0.3")),
                "ETH", only="nope", market="market"
                )
        self.assertEqual(False, result[0])
        self.assertIsInstance(result[1], portfolio.Trade)

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

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

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

    @mock.patch.object(portfolio.ReportStore, "log_orders")
    def test_prepare_orders(self, log_orders):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()

        trade_mock1.prepare_order.return_value = 1
        trade_mock2.prepare_order.return_value = 2

        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")
        log_orders.assert_called_once_with([1, 2], None, "default")

        log_orders.reset_mock()

        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")
        log_orders.assert_called_once_with([1, 2], None, "bla")

        trade_mock1.prepare_order.reset_mock()
        trade_mock2.prepare_order.reset_mock()
        log_orders.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")
        log_orders.assert_called_once_with([2], "bar", "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.ReportStore, "log_stage")
    @mock.patch.object(portfolio.ReportStore, "log_orders")
    @mock.patch.object(portfolio.TradeStore, "all_orders")
    def test_run_orders(self, all_orders, log_orders, log_stage):
        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()

        log_stage.assert_called_with("run_orders")
        log_orders.assert_called_with([order_mock1, order_mock2,
            order_mock3])

    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")
    @mock.patch("portfolio.ReportStore.log_tickers")
    def test_in_currency(self, log_tickers, 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)
        log_tickers.assert_called_once_with(market, amounts, "BTC",
                "average", "total")
        log_tickers.reset_mock()

        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)
        log_tickers.assert_called_once_with(market, amounts, "BTC",
                "bid", "total")
        log_tickers.reset_mock()

        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)
        log_tickers.assert_called_once_with(market, amounts, "BTC",
                "bid", "exchange_used")
        log_tickers.reset_mock()

    @mock.patch.object(portfolio.ReportStore, "log_balances")
    def test_fetch_balances(self, log_balances):
        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, tag="foo")
        self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
        self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))
        log_balances.assert_called_with(market, tag="foo")

    @mock.patch.object(portfolio.Portfolio, "repartition")
    @mock.patch.object(portfolio.ReportStore, "log_balances")
    @mock.patch("store.ReportStore.log_dispatch")
    def test_dispatch_assets(self, log_dispatch, log_balances, 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_hash = {
                "XEM": (D("0.75"), "long"),
                "BTC": (D("0.26"), "long"),
                "DASH": (D("0.10"), "short"),
                }
        repartition.return_value = repartition_hash

        amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
        repartition.assert_called_with(liquidity="medium")
        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)
        log_balances.assert_called_with(market, tag=None)
        log_dispatch.assert_called_once_with(portfolio.Amount("BTC",
            "11.1"), amounts, "medium", repartition_hash)

    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()))

    def test_as_json(self):
        balance_mock1 = mock.Mock()
        balance_mock1.as_json.return_value = 1

        balance_mock2 = mock.Mock()
        balance_mock2.as_json.return_value = 2

        portfolio.BalanceStore.all = {
                "BTC": balance_mock1,
                "ETH": balance_mock2,
                }

        as_json = portfolio.BalanceStore.as_json()
        self.assertEqual(1, as_json["BTC"])
        self.assertEqual(2, as_json["ETH"])


@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("portfolio.ReportStore") as report_store:
            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))
            report_store.log_error.assert_called_with("prepare_order", message=mock.ANY)
            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.object(portfolio.ReportStore, "log_order") as log_order:
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                log_order.assert_called_once_with(order_mock, i,
                        update="waiting", compute_value=None, new_order=None)

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

        with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
            trade.update_order(order_mock, 2)
            order_mock.cancel.assert_called()
            new_order_mock.run.assert_called()
            prepare_order.assert_called()
            log_order.assert_called()
            self.assertEqual(2, log_order.call_count)
            calls = [
                    mock.call(order_mock, 2, update="adjusting",
                        compute_value='lambda x, y: (x[y] + x["average"]) / 2',
                        new_order=new_order_mock),
                    mock.call(order_mock, 2, new_order=new_order_mock),
                    ]
            log_order.assert_has_calls(calls)

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

        with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
            trade.update_order(order_mock, 5)
            order_mock.cancel.assert_called()
            new_order_mock.run.assert_called()
            prepare_order.assert_called()
            self.assertEqual(2, log_order.call_count)
            log_order.assert_called()
            calls = [
                    mock.call(order_mock, 5, update="adjusting",
                        compute_value='lambda x, y: (x[y]*2 + x["average"]) / 3',
                        new_order=new_order_mock),
                    mock.call(order_mock, 5, new_order=new_order_mock),
                    ]
            log_order.assert_has_calls(calls)

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

        with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
            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")
            log_order.assert_called()
            self.assertEqual(2, log_order.call_count)
            calls = [
                    mock.call(order_mock, 7, update="market_fallback",
                        compute_value='default',
                        new_order=new_order_mock),
                    mock.call(order_mock, 7, new_order=new_order_mock),
                    ]
            log_order.assert_has_calls(calls)

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

        for i in [10, 13, 16]:
            with self.subTest(tick=i), mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
                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")
                log_order.assert_called()
                self.assertEqual(2, log_order.call_count)
                calls = [
                        mock.call(order_mock, i, update="market_adjust",
                            compute_value='default',
                            new_order=new_order_mock),
                        mock.call(order_mock, i, new_order=new_order_mock),
                        ]
                log_order.assert_has_calls(calls)

            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.object(portfolio.ReportStore, "log_order") as log_order:
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                log_order.assert_called_once_with(order_mock, i, update="waiting",
                        compute_value=None, new_order=None)

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


    @mock.patch.object(portfolio.ReportStore, "print_log")
    def test_print_with_order(self, print_log):
        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"
        order_mock1.mouvements = []
        mouvement_mock1 = mock.Mock()
        mouvement_mock1.__repr__ = mock.Mock()
        mouvement_mock1.__repr__.return_value = "Mouvement 1"
        mouvement_mock2 = mock.Mock()
        mouvement_mock2.__repr__ = mock.Mock()
        mouvement_mock2.__repr__.return_value = "Mouvement 2"
        order_mock2.mouvements = [
                mouvement_mock1, mouvement_mock2
                ]
        trade.orders.append(order_mock1)
        trade.orders.append(order_mock2)

        trade.print_with_order()

        print_log.assert_called()
        calls = print_log.mock_calls
        self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0]))
        self.assertEqual("\tMock 1", str(calls[1][1][0]))
        self.assertEqual("\tMock 2", str(calls[2][1][0]))
        self.assertEqual("\t\tMouvement 1", str(calls[3][1][0]))
        self.assertEqual("\t\tMouvement 2", str(calls[4][1][0]))

    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))

    def test_as_json(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")

        as_json = trade.as_json()
        self.assertEqual("acquire", as_json["action"])
        self.assertEqual(D("0.5"), as_json["from"])
        self.assertEqual(D("1.0"), as_json["to"])
        self.assertEqual("ETH", as_json["currency"])
        self.assertEqual("BTC", as_json["base_currency"])

@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_as_json(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", "market", "trade")
        mouvement_mock1 = mock.Mock()
        mouvement_mock1.as_json.return_value = 1
        mouvement_mock2 = mock.Mock()
        mouvement_mock2.as_json.return_value = 2

        order.mouvements = [mouvement_mock1, mouvement_mock2]
        as_json = order.as_json()
        self.assertEqual("buy", as_json["action"])
        self.assertEqual("long", as_json["trade_type"])
        self.assertEqual(10, as_json["amount"])
        self.assertEqual("ETH", as_json["currency"])
        self.assertEqual("BTC", as_json["base_currency"])
        self.assertEqual(D("0.1"), as_json["rate"])
        self.assertEqual("pending", as_json["status"])
        self.assertEqual(False, as_json["close_if_possible"])
        self.assertIsNone(as_json["id"])
        self.assertEqual([1, 2], as_json["mouvements"])

    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")
    @mock.patch("portfolio.ReportStore")
    def test_cancel(self, report_store, 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()
        report_store.log_debug_action.assert_called_once()
        report_store.log_debug_action.reset_mock()
        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()
        report_store.log_debug_action.assert_not_called()

    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", {
            "tradeID": 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", {
            "tradeID": 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 = [
                {
                    "tradeID": 42, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 12:00:12", "rate": "0.1",
                    "amount": "3", "total": "0.3"
                    },
                {
                    "tradeID": 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)

        market.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", market, "trade")
        order.fetch_mouvements()
        self.assertEqual(0, len(order.mouvements))

    @mock.patch("portfolio.ReportStore")
    def test_mark_finished_order(self, report_store):
        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()
        report_store.log_debug_action.assert_called_once()

    @mock.patch.object(portfolio.Order, "fetch_mouvements")
    @mock.patch("portfolio.ReportStore")
    def test_fetch(self, report_store, 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()
                report_store.log_debug_action.assert_called_once()
                report_store.log_debug_action.reset_mock()
                order.fetch(force=True)
                time_mock.assert_not_called()
                market.fetch_order.assert_not_called()
                fetch_mouvements.assert_not_called()
                report_store.log_debug_action.assert_called_once()
                report_store.log_debug_action.reset_mock()
            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()
                report_store.log_debug_action.assert_not_called()

    @mock.patch.object(portfolio.Order, "fetch")
    @mock.patch.object(portfolio.Order, "mark_finished_order")
    @mock.patch("portfolio.ReportStore")
    def test_get_status(self, report_store, 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()
            report_store.log_debug_action.assert_called_once()

        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('portfolio.ReportStore') as report_store:
            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()
            report_store.log_debug_action.assert_called_with("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
            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('portfolio.ReportStore') as report_store:
            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)
            report_store.log_error.assert_called_once()

        market.create_order.reset_mock()
        with self.subTest(dust_amount_exception=True),\
                mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
            order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
                    D("0.1"), "BTC", "long", market, "trade")
            market.create_order.side_effect = portfolio.ExchangeNotAvailable
            order.run()
            market.create_order.assert_called_once()
            self.assertEqual(0, len(order.results))
            self.assertEqual("closed", order.status)
            mark_finished_order.assert_called_once()


@unittest.skipUnless("unit" in limits, "Unit skipped")
class MouvementTest(WebMockTestCase):
    def test_values(self):
        mouvement = portfolio.Mouvement("ETH", "BTC", {
            "tradeID": 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)

        mouvement = portfolio.Mouvement("ETH", "BTC", { "foo": "bar" })
        self.assertIsNone(mouvement.date)
        self.assertIsNone(mouvement.id)
        self.assertIsNone(mouvement.action)
        self.assertEqual(-1, mouvement.fee_rate)
        self.assertEqual(0, mouvement.rate)
        self.assertEqual(portfolio.Amount("ETH", 0), mouvement.total)
        self.assertEqual(portfolio.Amount("BTC", 0), mouvement.total_in_base)

    def test__repr(self):
        mouvement = portfolio.Mouvement("ETH", "BTC", {
            "tradeID": 42, "type": "buy", "fee": "0.0015",
            "date": "2017-12-30 12:00:12", "rate": "0.1",
            "amount": "10", "total": "1"
            })
        self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement))

        mouvement = portfolio.Mouvement("ETH", "BTC", {
            "tradeID": 42, "type": "buy",
            "date": "garbage", "rate": "0.1",
            "amount": "10", "total": "1"
            })
        self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement))

    def test_as_json(self):
        mouvement = portfolio.Mouvement("ETH", "BTC", {
            "tradeID": 42, "type": "buy", "fee": "0.0015",
            "date": "2017-12-30 12:00:12", "rate": "0.1",
            "amount": "10", "total": "1"
            })
        as_json = mouvement.as_json()

        self.assertEqual(D("0.0015"), as_json["fee_rate"])
        self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), as_json["date"])
        self.assertEqual("buy", as_json["action"])
        self.assertEqual(D("10"), as_json["total"])
        self.assertEqual(D("1"), as_json["total_in_base"])
        self.assertEqual("BTC", as_json["base_currency"])
        self.assertEqual("ETH", as_json["currency"])

@unittest.skipUnless("unit" in limits, "Unit skipped")
class ReportStoreTest(WebMockTestCase):
    def test_add_log(self):
        portfolio.ReportStore.add_log({"foo": "bar"})

        self.assertEqual({"foo": "bar", "date": mock.ANY}, portfolio.ReportStore.logs[0])

    def test_set_verbose(self):
        with self.subTest(verbose=True):
            portfolio.ReportStore.set_verbose(True)
            self.assertTrue(portfolio.ReportStore.verbose_print)

        with self.subTest(verbose=False):
            portfolio.ReportStore.set_verbose(False)
            self.assertFalse(portfolio.ReportStore.verbose_print)

    def test_print_log(self):
        with self.subTest(verbose=True),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            portfolio.ReportStore.set_verbose(True)
            portfolio.ReportStore.print_log("Coucou")
            portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1))
            self.assertEqual(stdout_mock.getvalue(), "Coucou\n1.00000000 BTC\n")

        with self.subTest(verbose=False),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            portfolio.ReportStore.set_verbose(False)
            portfolio.ReportStore.print_log("Coucou")
            portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1))
            self.assertEqual(stdout_mock.getvalue(), "")

    def test_to_json(self):
        portfolio.ReportStore.logs.append({"foo": "bar"})
        self.assertEqual('[{"foo": "bar"}]', portfolio.ReportStore.to_json())
        portfolio.ReportStore.logs.append({"date": portfolio.datetime(2018, 2, 24)})
        self.assertEqual('[{"foo": "bar"}, {"date": "2018-02-24T00:00:00"}]', portfolio.ReportStore.to_json())
        portfolio.ReportStore.logs.append({"amount": portfolio.Amount("BTC", 1)})
        with self.assertRaises(TypeError):
            portfolio.ReportStore.to_json()

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_stage(self, add_log, print_log):
        portfolio.ReportStore.log_stage("foo")
        print_log.assert_has_calls([
            mock.call("-----------"),
            mock.call("[Stage] foo"),
            ])
        add_log.assert_called_once_with({'type': 'stage', 'stage': 'foo'})

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    @mock.patch("store.BalanceStore")
    def test_log_balances(self, balance_store, add_log, print_log):
        balance_store.as_json.return_value = "json"
        balance_store.all = { "FOO": "bar", "BAR": "baz" }

        portfolio.ReportStore.log_balances("market", tag="tag")
        print_log.assert_has_calls([
            mock.call("[Balance]"),
            mock.call("\tbar"),
            mock.call("\tbaz"),
            ])
        add_log.assert_called_once_with({
            'type': 'balance',
            'balances': 'json',
            'tag': 'tag'
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_tickers(self, add_log, print_log):
        market = mock.Mock()
        amounts = {
                "BTC": portfolio.Amount("BTC", 10),
                "ETH": portfolio.Amount("BTC", D("0.3"))
                }
        amounts["ETH"].rate = D("0.1")

        portfolio.ReportStore.log_tickers(market, amounts, "BTC", "default", "total")
        print_log.assert_not_called()
        add_log.assert_called_once_with({
            'type': 'tickers',
            'compute_value': 'default',
            'balance_type': 'total',
            'currency': 'BTC',
            'balances': {
                'BTC': D('10'),
                'ETH': D('0.3')
                },
            'rates': {
                'BTC': None,
                'ETH': D('0.1')
                },
            'total': D('10.3')
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_dispatch(self, add_log, print_log):
        amount = portfolio.Amount("BTC", "10.3")
        amounts = {
                "BTC": portfolio.Amount("BTC", 10),
                "ETH": portfolio.Amount("BTC", D("0.3"))
                }
        portfolio.ReportStore.log_dispatch(amount, amounts, "medium", "repartition")
        print_log.assert_not_called()
        add_log.assert_called_once_with({
            'type': 'dispatch',
            'liquidity': 'medium',
            'repartition_ratio': 'repartition',
            'total_amount': {
                'currency': 'BTC',
                'value': D('10.3')
                },
            'repartition': {
                'BTC': D('10'),
                'ETH': D('0.3')
                }
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_trades(self, add_log, print_log):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()
        trade_mock1.as_json.return_value = { "trade": "1" }
        trade_mock2.as_json.return_value = { "trade": "2" }

        matching_and_trades = [
                (True, trade_mock1),
                (False, trade_mock2),
                ]
        portfolio.ReportStore.log_trades(matching_and_trades, "only", "debug")

        print_log.assert_not_called()
        add_log.assert_called_with({
            'type': 'trades',
            'only': 'only',
            'debug': 'debug',
            'trades': [
                {'trade': '1', 'skipped': False},
                {'trade': '2', 'skipped': True}
                ]
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
    def test_log_orders(self, print_all_with_order, add_log, print_log):
        order_mock1 = mock.Mock()
        order_mock2 = mock.Mock()

        order_mock1.as_json.return_value = "order1"
        order_mock2.as_json.return_value = "order2"

        orders = [order_mock1, order_mock2]

        portfolio.ReportStore.log_orders(orders, tick="tick",
                only="only", compute_value="compute_value")

        print_log.assert_called_once_with("[Orders]")
        print_all_with_order.assert_called_once_with(ind="\t")

        add_log.assert_called_with({
            'type': 'orders',
            'only': 'only',
            'compute_value': 'compute_value',
            'tick': 'tick',
            'orders': ['order1', 'order2']
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_order(self, add_log, print_log):
        order_mock = mock.Mock()
        order_mock.as_json.return_value = "order"
        new_order_mock = mock.Mock()
        new_order_mock.as_json.return_value = "new_order"
        order_mock.__repr__ = mock.Mock()
        order_mock.__repr__.return_value = "Order Mock"
        new_order_mock.__repr__ = mock.Mock()
        new_order_mock.__repr__.return_value = "New order Mock"

        with self.subTest(finished=True):
            portfolio.ReportStore.log_order(order_mock, 1, finished=True)
            print_log.assert_called_once_with("[Order] Finished Order Mock")
            add_log.assert_called_once_with({
                'type': 'order',
                'tick': 1,
                'update': None,
                'order': 'order',
                'compute_value': None,
                'new_order': None
                })

        add_log.reset_mock()
        print_log.reset_mock()

        with self.subTest(update="waiting"):
            portfolio.ReportStore.log_order(order_mock, 1, update="waiting")
            print_log.assert_called_once_with("[Order] Order Mock, tick 1, waiting")
            add_log.assert_called_once_with({
                'type': 'order',
                'tick': 1,
                'update': 'waiting',
                'order': 'order',
                'compute_value': None,
                'new_order': None
                })

        add_log.reset_mock()
        print_log.reset_mock()
        with self.subTest(update="adjusting"):
            portfolio.ReportStore.log_order(order_mock, 3,
                    update="adjusting", new_order=new_order_mock,
                    compute_value="default")
            print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
            add_log.assert_called_once_with({
                'type': 'order',
                'tick': 3,
                'update': 'adjusting',
                'order': 'order',
                'compute_value': "default",
                'new_order': 'new_order'
                })

        add_log.reset_mock()
        print_log.reset_mock()
        with self.subTest(update="market_fallback"):
            portfolio.ReportStore.log_order(order_mock, 7,
                    update="market_fallback", new_order=new_order_mock)
            print_log.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value")
            add_log.assert_called_once_with({
                'type': 'order',
                'tick': 7,
                'update': 'market_fallback',
                'order': 'order',
                'compute_value': None,
                'new_order': 'new_order'
                })

        add_log.reset_mock()
        print_log.reset_mock()
        with self.subTest(update="market_adjusting"):
            portfolio.ReportStore.log_order(order_mock, 17,
                    update="market_adjust", new_order=new_order_mock)
            print_log.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock")
            add_log.assert_called_once_with({
                'type': 'order',
                'tick': 17,
                'update': 'market_adjust',
                'order': 'order',
                'compute_value': None,
                'new_order': 'new_order'
                })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_move_balances(self, add_log, print_log):
        needed = {
                "BTC": portfolio.Amount("BTC", 10),
                "USDT": 1
                }
        moving = {
                "BTC": portfolio.Amount("BTC", 3),
                "USDT": -2
                }
        portfolio.ReportStore.log_move_balances(needed, moving, True)
        print_log.assert_not_called()
        add_log.assert_called_once_with({
            'type': 'move_balances',
            'debug': True,
            'needed': {
                'BTC': D('10'),
                'USDT': 1
                },
            'moving': {
                'BTC': D('3'),
                'USDT': -2
                }
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_http_request(self, add_log, print_log):
        response = mock.Mock()
        response.status_code = 200
        response.text = "Hey"

        portfolio.ReportStore.log_http_request("method", "url", "body",
                "headers", response)
        print_log.assert_not_called()
        add_log.assert_called_once_with({
            'type': 'http_request',
            'method': 'method',
            'url': 'url',
            'body': 'body',
            'headers': 'headers',
            'status': 200,
            'response': 'Hey'
            })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_error(self, add_log, print_log):
        with self.subTest(message=None, exception=None):
            portfolio.ReportStore.log_error("action")
            print_log.assert_called_once_with("[Error] action")
            add_log.assert_called_once_with({
                'type': 'error',
                'action': 'action',
                'exception_class': None,
                'exception_message': None,
                'message': None
                })

        print_log.reset_mock()
        add_log.reset_mock()
        with self.subTest(message="Hey", exception=None):
            portfolio.ReportStore.log_error("action", message="Hey")
            print_log.assert_has_calls([
                    mock.call("[Error] action"),
                    mock.call("\tHey")
                    ])
            add_log.assert_called_once_with({
                'type': 'error',
                'action': 'action',
                'exception_class': None,
                'exception_message': None,
                'message': "Hey"
                })

        print_log.reset_mock()
        add_log.reset_mock()
        with self.subTest(message=None, exception=Exception("bouh")):
            portfolio.ReportStore.log_error("action", exception=Exception("bouh"))
            print_log.assert_has_calls([
                    mock.call("[Error] action"),
                    mock.call("\tException: bouh")
                    ])
            add_log.assert_called_once_with({
                'type': 'error',
                'action': 'action',
                'exception_class': "Exception",
                'exception_message': "bouh",
                'message': None
                })

        print_log.reset_mock()
        add_log.reset_mock()
        with self.subTest(message="Hey", exception=Exception("bouh")):
            portfolio.ReportStore.log_error("action", message="Hey", exception=Exception("bouh"))
            print_log.assert_has_calls([
                    mock.call("[Error] action"),
                    mock.call("\tException: bouh"),
                    mock.call("\tHey")
                    ])
            add_log.assert_called_once_with({
                'type': 'error',
                'action': 'action',
                'exception_class': "Exception",
                'exception_message': "bouh",
                'message': "Hey"
                })

    @mock.patch.object(portfolio.ReportStore, "print_log")
    @mock.patch.object(portfolio.ReportStore, "add_log")
    def test_log_debug_action(self, add_log, print_log):
        portfolio.ReportStore.log_debug_action("Hey")

        print_log.assert_called_once_with("[Debug] Hey")
        add_log.assert_called_once_with({
            'type': 'debug_action',
            'action': 'Hey'
            })

@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
class AcceptanceTest(WebMockTestCase):
    @unittest.expectedFailure
    def test_success_sell_only_necessary(self):
        # FIXME: catch stdout
        portfolio.ReportStore.verbose_print = False
        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 = [
                {
                    "tradeID": 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()