aboutsummaryrefslogblamecommitdiff
path: root/test.py
blob: 854e27b1089bf6f985778242a550eca9340a0c25 (plain) (tree)
1
2
3
4
5
6
7
8
9
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
          

                
               
                                
                         

                    
                       
                
                                     
 









                                                     


                                         


                                                                                
 
                    
                       


                                        



                                                             
                         
                                                     







                                                    
                                                          
                                                                     



                                     



                                     
                          
 
                                                      

                                       
                       






                                        
                          
 
                         

                                                                       









                                                                                         














                                                                                         









                                                                   








                                                                                                                    
                                                                                              






                                                                                                                     
                                                                                              






                                                                                                                      
                                                                                              






                                                                                                                      
                                                                                              

















                                                                                            

























































                                                                                      






















































































































































































































































































































                                                                                                                                                        
                                                      




























































































                                                                                     
                                     
                    
                       
 
                                                                 

                                               
                                                                  
 

                                                                 

























































                                                                                            
 
                                         




























































                                                                                               


                                                               
                                                        







                                                      

                                                                  
 



                                                                                               
 


                                                               
                           




                                         
                                
                                                                              
                                      
                                                   
                                              
 
                                          

                                             

                                                                                                           

                          
                                        
                                                          
                           
                                                  
                                 
                                                           

                          
                                        
                                                          
                           
                                                 

                                             
                                                            
 































                                                                                         
                                                      
                                  
                          

                                                 

                                                


                                            
                                                                   
 

                                                         
 
                                                                           
 

                                               

                                    
                                        

                                 
                                                                
 
                                                              



                                                                   
                                                                                                       

                                                            
                                                                                     

                                                            
                                                                            

                                                          




                                                                      









                                                    

                                                 



                                                             
                                                





                                                    

                                              
                         
                                                





                                            

                                                 
 
                                                             

                                                             
                                                





                                                    




                                               
                                                                   
 


                                            

                                                              






                                            

                                                              



                                            

                                                             
 


                                          
                            

                                            

                                                             












                                               



































                                               
















                                               





















                                                                



















                                                                                                                           










                                                                                      
                                                      
                                   
                          




                                            


                                      














                                                                           

                                                                          

                                                                 
                                                                  
 

                                                 


                                                                               





                                                                                                                      
 


                                                                                      
                                                               

                                                                                                                        
 
                                                                                        

                                                                                   




                                                                                                                  
 
                                                               

                                                                                                                                     
 








                                                                                       
                                                           

                                                          
                                                      

                                  
                       



                                                         
                                                        










                                                              
                                                                  

                                
                                                                   

                                 



                                                                           

                                                              


                                                                          
                                                              
 



                                                   
 
                                                                                                 


                                               
                                                                                                       

                                       



                                               
                 
 
                                                        


                                                    
 







                                                                  
                                                            











                                                           
                                                               













                                                       
                                                            












                                                                
                                                               


                                               
 
                              
                                                        





                                                 
 
                                                       


                                                                           
                                    

                                           
                 
                                





                                                   
                                                                    

                                            
                                              
                                                            













                                                         
 
                                                
 







                                                                  

                                                                        
                                                                    
                                                                    
 
 
                                            
                                                       
                                                        



                                                          
                                                     
                                                                           
 























                                                                               
                                            












                                                                              
                                                  






                                                          


                                                                   




                                                                      
                                                           



                                                                 
                                                          


                                 
                                                                                                    





                                                        































                                                                                         
                                                             


                                                
                                                     
                                                                           
 


                                                                      
                                                                        



                                                                     
                                                                        



                                                                     
                                                                        
 
                                                       


                                                                                                               
                                                                                     
 
                                 
 

                                                               
 
                         

                                                                             
                     
                                                                                              

                                                                                                 
 
















                                                                                                          
                                                 




                                                              
                                                              

                                                             
                                                              
                                                                                                       
                                                                          









































































































                                                                                                                                          
                                     
                                    

                                                                   




                                                                   
                                                                                    

                                                        
                                                               

                                                                                


                                                                               

                                                      
                                                                                      

                                                        
                                                          


                                                                               








































                                                                                                                                                                                        
                                                                                  










                                                                            
                                                                                                         







                                                                            

                            
                                                                   



                                                                               
















                                                                                                        














                                                                             

                                                                                             












                                                                               

                                
                                                        












                                                                                   
                                                        























                                                                                   
                                                        
















































































                                                                                                 
                                                      
                                      

                                                                                      












                                                          






                           
 


                                                                                            
 






                                                                            
 
                                     




































                                                             

                                 
                                 
 

                                                  

                                                  


                                   
 

                                           
                                           
 
                                    

                                                                             
                                                     
                                                                                 
 
                                             
 
                                                       

                                                                         
                                                                             


                                              
                                             


                                  
                                              

                                                                             
                                                                               




                                        

                                                                 
 
                                          




                                                    











                                                                              




                                       

                                                                              

                         














                                                       


                                               
 
                                         

                                        
                                                          


                                                                 











                                                                              
 


                                                  
 
















                                                   

                                 
                                  
                                 
                                  
                                 
                                   







                                                                         
 
                                                      

                                        
                       




























                                               








                                                   










                                                 
 
                                                  


                                                         
                                                                         
                                   
                                              
 
                                                                       

                                                         
                                                                         
                               
                                              
 
                                                                                             

                                                         
                                                                         
                                       
                                              
 

                                                                        
 
                                                   
 




                                                                                      

                                                        



                                                                                             
 
                                                       




                                                                        
 
                                                           
 
                            

                                           
                                             
                 
                                                   
 
                                                                                
                                                          
                                                        

                                                        
                                                          

                                                                                  
                                                         

                              


                                                   










                                                 
                                                                              
 






                                              

                                                   



                                     
                                         



                                           
                                                      























                                                                                 
                                                      
                                 
 
                                    


                                                              
                                                                    

                                                    
                                              

                                               
                                                                 
                                               
                                                                

                                               
                                                                



                                                                 
 
                                               
                                                                    
                                                       
 
                          


                                                              
                                                                    
 



                                                             
                                                 
                                                                    





                                                              
                                                                    





                                                           
                                                                    
 
                                                 
 
                                


                                                              
                                                                    
 



                                                      



                                                           
                                                                    
 



                                                      




                                                              
                                                                    





                                                           
                                                                    


                                                   
                                




                                                                        
 

                                                                              
 















                                                                               
 





                                                                         
 






                                                                               
 
                                                
 


                                                                               
 
                                               
 


                                                                           
 
 



                                                              
                                                                    

                            
                                                                          

                            
                                                                           


                                   


                                                                                
 







                                                                                                      


                                                              
                                                                      






                                                                 
                                                                        







                                                  

                                                              






                                                                       
                                                                        



                                                                    
                                                                                                             
                                                  
                                                                                         









                                                                      
                                                                        



                                                                    
                                                                                                             

                                                                         
                                                      

                                                   


















                                                                                                             







                                                                     
                                                                        



                                                                   
                                                                                                            


                                                                        
                                                  









                                                                     
                                                                        



                                                                    
                                                                                                             

                                                                          
                                                      

                                                  
                                                                             







                                                                       
                                                                        



                                                                   
                                                                                                                    

                                                                              
                                                    









                                                                     
                                                                        



                                                                    
                                                                                                                     

                                                                             
                                                    

                                                   














                                                                                  








                                                              
                                                                    
                                                   

                                 
                                      


                                                      
                                                                              
                                                                             



                                       









                                                               
                                           



                                                                   



                                   









                                                               
                                           



                                                                   



                                   














                                                                   



                                   
                                            

                              
                                      



                                                                         

                                                                       





                                                                           
                                                               



                                       
                                                

                                
                                      


                                                      
                                                                                                
                                                           



                                       
                                                
 
 
                                    


                                                              
                                                                    






                                                    









                                                             


                                        

                                                                 
 























                                                                                                                                  
 




                                                                    

                                   



                                            
                                               

















                                                                          
                         


                                                              
                                                                    

                                                                                                                 
 



                                                              
                                                                    







                                                         


























                                                                                                 




















                                                                   


































                                                                    
                                 




                                                                       
 




                                                               
 






































                                                                          


                                                                   
                                                         









                                                                                                 
                                                         

                                                           


                                                           



                                                                   
                                                         
                                                                   
                                                          



                                                                   
                                                          










                                                                                                    
                                                                 
                 
                                                                  
                                                                 


                                                 
                                                                  
                                                                 



                                                                   
                                                         




                                                
                                                                                        
                                                  

                                                    
 
                                                                                      
                                                                   
                                                         


                                                  
                                       
                                                                   
                                                          

                                       
                            

                                   

                                                                          


                                   
                                                             

                                                                   
                                                          


                                        
                                                             

                                                                    
                                                          


                                       
                                                             

                                                                   
                                                         


                                       
                                                             
 
                           

                                                                   
                                                          



                                       

                                                             

                                                           
                                                                 
                                                              
                                                                                        








                                                                   
                                                   
                                                      
                                                
 






                                                    
 
                                                               




                                                              
                                                    
                                                       
 
                                            



                                                       
                             
                                                                
                                                        
 
                                          













                                                                       

                                                     















                                                                       

                                                    

























                                                                       

                                                               















                                                                       

                                                               
 
                                                
                                     
                                      
                               
                                                                       
                                                             

                                                           
                                                               

                                                       
                                
                                                                       
                                                             





                                                        

                                      

                                                      
                                
                                                                       
                                                             





                                                          


                                      


                                                    
                                                                       
                                                             
                       

                                                                                                                                                          



                                                   
                                             
                                       
                                
                                                                       

                                                                 
                       
                                                         


                                                   

                                             
                                                                       

                                                                    
                       
                                                         

                                                   
                                                        
 
                                             


                                                                                                 
                                                             
                                                                         
                       
                                                         



                                                    
                                                    
                                             
                                                    
                                                                                                 
                                                                            
                                                             





                                                    
                       



































                                                                                                          
                                                   




                                                                                                                                                             
 
                           









































                                                                                                                                                                                     



















































































































































































































































































































































































































































































































                                                                                                                                                                                
 



                                                       
                                                          












                                                                                     








                                                                             






                                                                                                                               
 






                                                                                                      


















                                                                                      

                                                 
 
                                                                                

                               
                                                 
                                        

                                                       

                                         

                                                        
 











                                                                                              
                                                          
 
                             
                                                 
                                         
                                                                   
                                                                               
                                                                                  


                                                              
                                                                                                                          


                                                                               


                                                              

                                                        







                                                                                  
                           

                                                 
                                                                                    
                                                                           
                                                                                                                                  
                                                                        
                                                                                                                                                                             
 













                                                                                              

                                                       
                                                 
                                                 

                                                                                   

                                     
                                                                                                              
              











                                         
 





                                                            
 
                                            




                                    




                                         
 

                                                       
                                                   
                                                 





                                                        
                                                                    
















                                         


















                                                                        

                                                       
                                                    
                                                 




                                                        
                                                                           














                                               

                                                       
                                                  
                                                 








                                                           
                                                            




                                     
                           





                                                 




                                                       







                                                   
                                                    


                                                           
                                                                            








                                             













                                                                                 

                                                       
                                                 
                                                 









                                                               
                                                                













                                                                            
                                                                   












                                                                                    
                                                               
                                                 
                                                                 
                                                





                                                                                                                       
                                                                                       





                                                    
                                                 













                                                                                                        
                                                  










                                                                                                                                      

                                                       
                                                         
                                                 







                                                   
                                                      


                                         
                           









                               

                                                       
                                                        
                                                 



                                  
                                                              











                                         














                                                              


                                                     
 
                                                                                


                                         
                                                                           

                           

              

                                                       
                                                 
                                                 
                                                        
                                            











                                                               
                                                           














                                                                     
                                                                         














                                                                      
                                                                                        












                                                   

                                                       
                                                        

                                                 






                                                        
                                                      
                                








                                                      
                                              























                                                                                                
                                                                    







                                                                    
                                                                     


















                                                                                                
                                                                   






                                                                
                                                                      





                                                                     
                                                                








                                                                
                                                                    




                                                                    


                                                                     

                                                                             
                                                                                    
                                                              





                                                                             
                                                                                    
                                                                          



                                                       

                                                         
                                                                               
 






                                       

                                                        
                                                                

                                                      
                                                                                              




                                                                              
                                                                    


                                                                            




                                                                       
 

                                          

                                                   
 
                                                       
 
                                                                                   
 
                                         
 
                                                             
                                                          
                                                                     
 

                                                       

                                                                       









                                                                        


                                                   
                                                       
 
                                                                                   



                                                             
                                                          





                                                                     
                                                                       
                                         
                                                                       
                      
 
                                        
                          









                                                    
 















                                                                      

                                               
 

                                            


                                                                 
                              





                                                       
 



                                                                      
 


                                                                               
                                                           













                                                                 
                                                                                                       








                                                               
                                                                                                                             

                                                      
 
 


                                                      
                                            
 
                                                  
 
                              
                                            
 


                                                                      
 
                                                     
 
                                                              
 
                                                     
 
                                                                 
 
                                                     

                               
                                            











                                                                                                                        
                                                
                                         
                                            

















                                                                               
                                                   
 
                                       

                                                                         
                                                                  
                                                      




















                                                                                                           
                                            
































                                                                                                             
                                            











































                                                                                                                             
 
 
                                                                  

                                      
                                               
                             
                                           

                         


                                               


                                      


                                               


                                      


                                                  



                                         





                                             


























                                            





                                            
                                    
                                          








                                                                          
                                                              
                                                      
                                                                                          
                      
                                         
 
                                             




                                                                              
                                         


                                                                                  
 


                                                                                  
 


                                                                                  
 

                                                                                  
                                                     
 


                                                                                  
 


                                                                                  

                  
                                                                                                             
 
                                                                     






                                                             
                                                                                       

                                           
                                                
                                                         

                                                     
                                                








                                                         
                                               

                  
                                         



                                                      


                                                                                                   
                                                                  



                                                                 
                                                              
                      
                                               







                                                    



                                                   


                                          



                                                 


                                        



                                               


                                      



                                               


                                      
                                                              
 
                                                                                          
                      
                                                                                  
 
                                             





                                                                                    



                                                                                  
 


                                                                                  


                                       


                                                                                  
 


                                                                                  
 


                                                                                  
 


                                                                                  

                  
                                                                                                
 
                                                                     

                                                                                                

                                                        
                                                          
 
                                                                                               
                                                         












                                                                                    
 



                                 

                  
                                           
 
                                                              
                      
                                               


                                        

                          
import sys
import portfolio
import unittest
import datetime
from decimal import Decimal as D
from unittest import mock
import requests
import requests_mock
from io import StringIO
import threading
import portfolio, market, main, store

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 market_args(self, debug=False, quiet=False, report_path=None, **kwargs):
        return main.configargparse.Namespace(report_path=report_path,
                debug=debug, quiet=quiet, **kwargs)

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

        # market
        self.m = mock.Mock(name="Market", spec=market.Market)
        self.m.debug = False

        self.patchers = [
                mock.patch.multiple(market.Portfolio,
                    data=store.LockedVar(None),
                    liquidities=store.LockedVar({}),
                    last_date=store.LockedVar(None),
                    report=mock.Mock(),
                    worker=None,
                    worker_notify=None,
                    worker_started=False,
                    callback=None),
                mock.patch.multiple(portfolio.Computation,
                    computations=portfolio.Computation.computations),
                ]
        for patcher in self.patchers:
            patcher.start()

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

@unittest.skipUnless("unit" in limits, "Unit skipped")
class poloniexETest(unittest.TestCase):
    def setUp(self):
        super().setUp()
        self.wm = requests_mock.Mocker()
        self.wm.start()

        self.s = market.ccxt.poloniexE()

    def tearDown(self):
        self.wm.stop()
        super().tearDown()

    def test__init(self):
        with self.subTest("Nominal case"), \
                mock.patch("market.ccxt.poloniexE.session") as session:
            session.request.return_value = "response"
            ccxt = market.ccxt.poloniexE()
            ccxt._market = mock.Mock
            ccxt._market.report = mock.Mock()

            ccxt.session.request("GET", "URL", data="data",
                    headers="headers")
            ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
                    'headers', 'response')

        with self.subTest("Raising"),\
                mock.patch("market.ccxt.poloniexE.session") as session:
            session.request.side_effect = market.ccxt.RequestException("Boo")

            ccxt = market.ccxt.poloniexE()
            ccxt._market = mock.Mock
            ccxt._market.report = mock.Mock()

            with self.assertRaises(market.ccxt.RequestException, msg="Boo") as cm:
                ccxt.session.request("GET", "URL", data="data",
                        headers="headers")
            ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
                    'headers', cm.exception)


    def test_nanoseconds(self):
        with mock.patch.object(market.ccxt.time, "time") as time:
            time.return_value = 123456.7890123456
            self.assertEqual(123456789012345, self.s.nanoseconds())

    def test_nonce(self):
        with mock.patch.object(market.ccxt.time, "time") as time:
            time.return_value = 123456.7890123456
            self.assertEqual(123456789012345, self.s.nonce())

    def test_request(self):
        with mock.patch.object(market.ccxt.poloniex, "request") as request,\
                mock.patch("market.ccxt.retry_call") as retry_call:
            with self.subTest(wrapped=True):
                with self.subTest(desc="public"):
                    self.s.request("foo")
                    retry_call.assert_called_with(request,
                            delay=1, tries=10, fargs=["foo"],
                            fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                    request.assert_not_called()

                with self.subTest(desc="private GET"):
                    self.s.request("foo", api="private")
                    retry_call.assert_called_with(request,
                            delay=1, tries=10, fargs=["foo"],
                            fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                    request.assert_not_called()

                with self.subTest(desc="private POST regexp"):
                    self.s.request("returnFoo", api="private", method="POST")
                    retry_call.assert_called_with(request,
                            delay=1, tries=10, fargs=["returnFoo"],
                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                    request.assert_not_called()

                with self.subTest(desc="private POST non-regexp"):
                    self.s.request("getMarginPosition", api="private", method="POST")
                    retry_call.assert_called_with(request,
                            delay=1, tries=10, fargs=["getMarginPosition"],
                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
                            exceptions=(market.ccxt.RequestTimeout, market.ccxt.InvalidNonce))
                    request.assert_not_called()
            retry_call.reset_mock()
            request.reset_mock()
            with self.subTest(wrapped=False):
                with self.subTest(desc="private POST non-matching regexp"):
                    self.s.request("marginBuy", api="private", method="POST")
                    request.assert_called_with("marginBuy",
                            api="private", method="POST", params={},
                            headers=None, body=None)
                    retry_call.assert_not_called()

                with self.subTest(desc="private POST non-matching non-regexp"):
                    self.s.request("closeMarginPositionOther", api="private", method="POST")
                    request.assert_called_with("closeMarginPositionOther",
                            api="private", method="POST", params={},
                            headers=None, body=None)
                    retry_call.assert_not_called()

    def test_order_precision(self):
        self.assertEqual(8, self.s.order_precision("FOO"))

    def test_transfer_balance(self):
        with self.subTest(success=True),\
                mock.patch.object(self.s, "privatePostTransferBalance") as t:
            t.return_value = { "success": 1 }
            result = self.s.transfer_balance("FOO", 12, "exchange", "margin")
            t.assert_called_once_with({
                "currency": "FOO",
                "amount": 12,
                "fromAccount": "exchange",
                "toAccount": "margin",
                "confirmed": 1
                })
            self.assertTrue(result)

        with self.subTest(success=False),\
                mock.patch.object(self.s, "privatePostTransferBalance") as t:
            t.return_value = { "success": 0 }
            self.assertFalse(self.s.transfer_balance("FOO", 12, "exchange", "margin"))

    def test_close_margin_position(self):
        with mock.patch.object(self.s, "privatePostCloseMarginPosition") as c:
            self.s.close_margin_position("FOO", "BAR")
            c.assert_called_with({"currencyPair": "BAR_FOO"})

    def test_tradable_balances(self):
        with mock.patch.object(self.s, "privatePostReturnTradableBalances") as r:
            r.return_value = {
                    "FOO": { "exchange": "12.1234", "margin": "0.0123" },
                    "BAR": { "exchange": "1", "margin": "0" },
                    }
            balances = self.s.tradable_balances()
            self.assertEqual(["FOO", "BAR"], list(balances.keys()))
            self.assertEqual(["exchange", "margin"], list(balances["FOO"].keys()))
            self.assertEqual(D("12.1234"), balances["FOO"]["exchange"])
            self.assertEqual(["exchange", "margin"], list(balances["BAR"].keys()))

    def test_margin_summary(self):
        with mock.patch.object(self.s, "privatePostReturnMarginAccountSummary") as r:
            r.return_value = {
                    "currentMargin": "1.49680968",
                    "lendingFees": "0.0000001",
                    "pl": "0.00008254",
                    "totalBorrowedValue": "0.00673602",
                    "totalValue": "0.01000000",
                    "netValue": "0.01008254",
                    }
            expected = {
                    'current_margin': D('1.49680968'),
                    'gains': D('0.00008254'),
                    'lending_fees': D('0.0000001'),
                    'total': D('0.01000000'),
                    'total_borrowed': D('0.00673602')
                    }
            self.assertEqual(expected, self.s.margin_summary())

    def test_create_order(self):
        with mock.patch.object(self.s, "create_exchange_order") as exchange,\
                mock.patch.object(self.s, "create_margin_order") as margin:
            with self.subTest(account="unspecified"):
                self.s.create_order("symbol", "type", "side", "amount", price="price", lending_rate="lending_rate", params="params")
                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
                margin.assert_not_called()
                exchange.reset_mock()
                margin.reset_mock()

            with self.subTest(account="exchange"):
                self.s.create_order("symbol", "type", "side", "amount", account="exchange", price="price", lending_rate="lending_rate", params="params")
                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
                margin.assert_not_called()
                exchange.reset_mock()
                margin.reset_mock()

            with self.subTest(account="margin"):
                self.s.create_order("symbol", "type", "side", "amount", account="margin", price="price", lending_rate="lending_rate", params="params")
                margin.assert_called_once_with("symbol", "type", "side", "amount", lending_rate="lending_rate", price="price", params="params")
                exchange.assert_not_called()
                exchange.reset_mock()
                margin.reset_mock()

            with self.subTest(account="unknown"), self.assertRaises(NotImplementedError):
                self.s.create_order("symbol", "type", "side", "amount", account="unknown")

    def test_parse_ticker(self):
        ticker = {
                "high24hr": "12",
                "low24hr": "10",
                "highestBid": "10.5",
                "lowestAsk": "11.5",
                "last": "11",
                "percentChange": "0.1",
                "quoteVolume": "10",
                "baseVolume": "20"
                }
        market = {
                "symbol": "BTC/ETC"
                }
        with mock.patch.object(self.s, "milliseconds") as ms:
            ms.return_value = 1520292715123
            result = self.s.parse_ticker(ticker, market)

            expected = {
                    "symbol": "BTC/ETC",
                    "timestamp": 1520292715123,
                    "datetime": "2018-03-05T23:31:55.123Z",
                    "high": D("12"),
                    "low": D("10"),
                    "bid": D("10.5"),
                    "ask": D("11.5"),
                    "vwap": None,
                    "open": None,
                    "close": None,
                    "first": None,
                    "last": D("11"),
                    "change": D("0.1"),
                    "percentage": None,
                    "average": None,
                    "baseVolume": D("10"),
                    "quoteVolume": D("20"),
                    "info": ticker
                    }
            self.assertEqual(expected, result)

    def test_fetch_margin_balance(self):
        with mock.patch.object(self.s, "privatePostGetMarginPosition") as get_margin_position:
            get_margin_position.return_value = {
                    "BTC_DASH": {
                        "amount": "-0.1",
                        "basePrice": "0.06818560",
                        "lendingFees": "0.00000001",
                        "liquidationPrice": "0.15107132",
                        "pl": "-0.00000371",
                        "total": "0.00681856",
                        "type": "short"
                        },
                    "BTC_ETC": {
                        "amount": "-0.6",
                        "basePrice": "0.1",
                        "lendingFees": "0.00000001",
                        "liquidationPrice": "0.6",
                        "pl": "0.00000371",
                        "total": "0.06",
                        "type": "short"
                        },
                    "BTC_ETH": {
                        "amount": "0",
                        "basePrice": "0",
                        "lendingFees": "0",
                        "liquidationPrice": "-1",
                        "pl": "0",
                        "total": "0",
                        "type": "none"
                        }
                    }
            balances = self.s.fetch_margin_balance()
            self.assertEqual(2, len(balances))
            expected = {
                "DASH": {
                    "amount": D("-0.1"),
                    "borrowedPrice": D("0.06818560"),
                    "lendingFees": D("1E-8"),
                    "pl": D("-0.00000371"),
                    "liquidationPrice": D("0.15107132"),
                    "type": "short",
                    "total": D("0.00681856"),
                    "baseCurrency": "BTC"
                    },
                "ETC": {
                    "amount": D("-0.6"),
                    "borrowedPrice": D("0.1"),
                    "lendingFees": D("1E-8"),
                    "pl": D("0.00000371"),
                    "liquidationPrice": D("0.6"),
                    "type": "short",
                    "total": D("0.06"),
                    "baseCurrency": "BTC"
                    }
                }
            self.assertEqual(expected, balances)

    def test_sum(self):
        self.assertEqual(D("1.1"), self.s.sum(D("1"), D("0.1")))

    def test_fetch_balance(self):
        with mock.patch.object(self.s, "load_markets") as load_markets,\
                mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balances,\
                mock.patch.object(self.s, "common_currency_code") as ccc:
            ccc.side_effect = ["ETH", "BTC", "DASH"]
            balances.return_value = {
                    "ETH": {
                        "available": "10",
                        "onOrders": "1",
                        },
                    "BTC": {
                        "available": "1",
                        "onOrders": "0",
                        },
                    "DASH": {
                        "available": "0",
                        "onOrders": "3"
                        }
                    }

            expected = {
                    "info": {
                        "ETH": {"available": "10", "onOrders": "1"},
                        "BTC": {"available": "1", "onOrders": "0"},
                        "DASH": {"available": "0", "onOrders": "3"}
                        },
                    "ETH": {"free": D("10"), "used": D("1"), "total": D("11")},
                    "BTC": {"free": D("1"), "used": D("0"), "total": D("1")},
                    "DASH": {"free": D("0"), "used": D("3"), "total": D("3")},
                    "free": {"ETH": D("10"), "BTC": D("1"), "DASH": D("0")},
                    "used": {"ETH": D("1"), "BTC": D("0"), "DASH": D("3")},
                    "total": {"ETH": D("11"), "BTC": D("1"), "DASH": D("3")}
                    }
            result = self.s.fetch_balance()
            load_markets.assert_called_once()
            self.assertEqual(expected, result)

    def test_fetch_balance_per_type(self):
        with mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balances:
            balances.return_value = {
                "exchange": {
                    "BLK": "159.83673869",
                    "BTC": "0.00005959",
                    "USDT": "0.00002625",
                    "XMR": "0.18719303"
                    },
                "margin": {
                    "BTC": "0.03019227"
                    }
                }
            expected = {
                    "info": {
                        "exchange": {
                            "BLK": "159.83673869",
                            "BTC": "0.00005959",
                            "USDT": "0.00002625",
                            "XMR": "0.18719303"
                            },
                        "margin": {
                            "BTC": "0.03019227"
                            }
                        },
                    "exchange": {
                        "BLK": D("159.83673869"),
                        "BTC": D("0.00005959"),
                        "USDT": D("0.00002625"),
                        "XMR": D("0.18719303")
                        },
                    "margin": {"BTC": D("0.03019227")},
                    "BLK": {"exchange": D("159.83673869")},
                    "BTC": {"exchange": D("0.00005959"), "margin": D("0.03019227")},
                    "USDT": {"exchange": D("0.00002625")},
                    "XMR": {"exchange": D("0.18719303")}
                    } 
            result = self.s.fetch_balance_per_type()
            self.assertEqual(expected, result)

    def test_fetch_all_balances(self):
        import json
        with mock.patch.object(self.s, "load_markets") as load_markets,\
                mock.patch.object(self.s, "privatePostGetMarginPosition") as margin_balance,\
                mock.patch.object(self.s, "privatePostReturnCompleteBalances") as balance,\
                mock.patch.object(self.s, "privatePostReturnAvailableAccountBalances") as balance_per_type:

            with open("test_samples/poloniexETest.test_fetch_all_balances.1.json") as f:
                balance.return_value = json.load(f)
            with open("test_samples/poloniexETest.test_fetch_all_balances.2.json") as f:
                margin_balance.return_value = json.load(f)
            with open("test_samples/poloniexETest.test_fetch_all_balances.3.json") as f:
                balance_per_type.return_value = json.load(f)

            result = self.s.fetch_all_balances()
            expected_doge = {
                    "total": D("-12779.79821852"),
                    "exchange_used": D("0E-8"),
                    "exchange_total": D("0E-8"),
                    "exchange_free": D("0E-8"),
                    "margin_available": 0,
                    "margin_in_position": 0,
                    "margin_borrowed": D("12779.79821852"),
                    "margin_total": D("-12779.79821852"),
                    "margin_pending_gain": 0,
                    "margin_lending_fees": D("-9E-8"),
                    "margin_pending_base_gain": D("0.00024059"),
                    "margin_position_type": "short",
                    "margin_liquidation_price": D("0.00000246"),
                    "margin_borrowed_base_price": D("0.00599149"),
                    "margin_borrowed_base_currency": "BTC"
                    } 
            expected_btc = {"total": D("0.05432165"),
                    "exchange_used": D("0E-8"),
                    "exchange_total": D("0.00005959"),
                    "exchange_free": D("0.00005959"),
                    "margin_available": D("0.03019227"),
                    "margin_in_position": D("0.02406979"),
                    "margin_borrowed": 0,
                    "margin_total": D("0.05426206"),
                    "margin_pending_gain": D("0.00093955"),
                    "margin_lending_fees": 0,
                    "margin_pending_base_gain": 0,
                    "margin_position_type": None,
                    "margin_liquidation_price": 0,
                    "margin_borrowed_base_price": 0,
                    "margin_borrowed_base_currency": None
                    }
            expected_xmr = {"total": D("0.18719303"),
                    "exchange_used": D("0E-8"),
                    "exchange_total": D("0.18719303"),
                    "exchange_free": D("0.18719303"),
                    "margin_available": 0,
                    "margin_in_position": 0,
                    "margin_borrowed": 0,
                    "margin_total": 0,
                    "margin_pending_gain": 0,
                    "margin_lending_fees": 0,
                    "margin_pending_base_gain": 0,
                    "margin_position_type": None,
                    "margin_liquidation_price": 0,
                    "margin_borrowed_base_price": 0,
                    "margin_borrowed_base_currency": None
                    } 
            self.assertEqual(expected_xmr, result["XMR"])
            self.assertEqual(expected_doge, result["DOGE"])
            self.assertEqual(expected_btc, result["BTC"])

    def test_create_margin_order(self):
        with self.assertRaises(market.ExchangeError):
            self.s.create_margin_order("FOO", "market", "buy", "10")

        with mock.patch.object(self.s, "load_markets") as load_markets,\
                mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
                mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
                mock.patch.object(self.s, "market") as market_mock,\
                mock.patch.object(self.s, "price_to_precision") as ptp,\
                mock.patch.object(self.s, "amount_to_precision") as atp:

            margin_buy.return_value = {
                    "orderNumber": 123
                    }
            margin_sell.return_value = {
                    "orderNumber": 456
                    }
            market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
            ptp.return_value = D("0.1")
            atp.return_value = D("12")

            order = self.s.create_margin_order("BTC_ETC", "margin", "buy", "12", price="0.1")
            self.assertEqual(123, order["id"])
            margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
            margin_sell.assert_not_called()
            margin_buy.reset_mock()
            margin_sell.reset_mock()

            order = self.s.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate="0.01", price="0.1")
            self.assertEqual(456, order["id"])
            margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
            margin_buy.assert_not_called()

    def test_create_exchange_order(self):
        with mock.patch.object(market.ccxt.poloniex, "create_order") as create_order:
            self.s.create_order("symbol", "type", "side", "amount", price="price", params="params")

            create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")

@unittest.skipUnless("unit" in limits, "Unit skipped")
class NoopLockTest(unittest.TestCase):
    def test_with(self):
        noop_lock = store.NoopLock()
        with noop_lock:
            self.assertTrue(True)

@unittest.skipUnless("unit" in limits, "Unit skipped")
class LockedVar(unittest.TestCase):

    def test_values(self):
        locked_var = store.LockedVar("Foo")
        self.assertIsInstance(locked_var.lock, store.NoopLock)
        self.assertEqual("Foo", locked_var.val)

    def test_get(self):
        with self.subTest(desc="Normal case"):
            locked_var = store.LockedVar("Foo")
            self.assertEqual("Foo", locked_var.get())
        with self.subTest(desc="Dict"):
            locked_var = store.LockedVar({"foo": "bar"})
            self.assertEqual({"foo": "bar"}, locked_var.get())
            self.assertEqual("bar", locked_var.get("foo"))
            self.assertIsNone(locked_var.get("other"))

    def test_set(self):
        locked_var = store.LockedVar("Foo")
        locked_var.set("Bar")
        self.assertEqual("Bar", locked_var.get())

    def test__getattr(self):
        dummy = type('Dummy', (object,), {})()
        dummy.attribute = "Hey"

        locked_var = store.LockedVar(dummy)
        self.assertEqual("Hey", locked_var.attribute)
        with self.assertRaises(AttributeError):
            locked_var.other

    def test_start_lock(self):
        locked_var = store.LockedVar("Foo")
        locked_var.start_lock()
        self.assertEqual("lock", locked_var.lock.__class__.__name__)

        thread1 = threading.Thread(target=locked_var.set, args=["Bar1"])
        thread2 = threading.Thread(target=locked_var.set, args=["Bar2"])
        thread3 = threading.Thread(target=locked_var.set, args=["Bar3"])

        with locked_var.lock:
            thread1.start()
            thread2.start()
            thread3.start()

            self.assertEqual("Foo", locked_var.val)
        thread1.join()
        thread2.join()
        thread3.join()
        self.assertEqual("Bar", locked_var.get()[0:3])

    def test_wait_for_notification(self):
        with self.assertRaises(RuntimeError):
            store.Portfolio.wait_for_notification()

        with mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\
                mock.patch.object(store.Portfolio, "report") as report,\
                mock.patch.object(store.time, "sleep") as sleep:
            store.Portfolio.start_worker(poll=3)

            store.Portfolio.worker_notify.set()

            store.Portfolio.callback.wait()

            report.print_log.assert_called_once_with("Fetching cryptoportfolio")
            get.assert_called_once_with(refetch=True)
            sleep.assert_called_once_with(3)
            self.assertFalse(store.Portfolio.worker_notify.is_set())
            self.assertTrue(store.Portfolio.worker.is_alive())

            store.Portfolio.callback.clear()
            store.Portfolio.worker_started = False
            store.Portfolio.worker_notify.set()
            store.Portfolio.callback.wait()

            self.assertFalse(store.Portfolio.worker.is_alive())

    def test_notify_and_wait(self):
        with mock.patch.object(store.Portfolio, "callback") as callback,\
                mock.patch.object(store.Portfolio, "worker_notify") as worker_notify:
            store.Portfolio.notify_and_wait()
            callback.clear.assert_called_once_with()
            worker_notify.set.assert_called_once_with()
            callback.wait.assert_called_once_with()

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

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

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

    @mock.patch.object(market.Portfolio, "parse_cryptoportfolio")
    def test_get_cryptoportfolio(self, parse_cryptoportfolio):
        with self.subTest(parallel=False):
            self.wm.get(market.Portfolio.URL, [
                {"text":'{ "foo": "bar" }', "status_code": 200},
                {"text": "System Error", "status_code": 500},
                {"exc": requests.exceptions.ConnectTimeout},
                ])
            market.Portfolio.get_cryptoportfolio()
            self.assertIn("foo", market.Portfolio.data.get())
            self.assertEqual("bar", market.Portfolio.data.get()["foo"])
            self.assertTrue(self.wm.called)
            self.assertEqual(1, self.wm.call_count)
            market.Portfolio.report.log_error.assert_not_called()
            market.Portfolio.report.log_http_request.assert_called_once()
            parse_cryptoportfolio.assert_called_once_with()
            market.Portfolio.report.log_http_request.reset_mock()
            parse_cryptoportfolio.reset_mock()
            market.Portfolio.data = store.LockedVar(None)

            market.Portfolio.get_cryptoportfolio()
            self.assertIsNone(market.Portfolio.data.get())
            self.assertEqual(2, self.wm.call_count)
            parse_cryptoportfolio.assert_not_called()
            market.Portfolio.report.log_error.assert_not_called()
            market.Portfolio.report.log_http_request.assert_called_once()
            market.Portfolio.report.log_http_request.reset_mock()
            parse_cryptoportfolio.reset_mock()

            market.Portfolio.data = store.LockedVar("Foo")
            market.Portfolio.get_cryptoportfolio()
            self.assertEqual(2, self.wm.call_count)
            parse_cryptoportfolio.assert_not_called()

            market.Portfolio.get_cryptoportfolio(refetch=True)
            self.assertEqual("Foo", market.Portfolio.data.get())
            self.assertEqual(3, self.wm.call_count)
            market.Portfolio.report.log_error.assert_called_once_with("get_cryptoportfolio",
                    exception=mock.ANY)
            market.Portfolio.report.log_http_request.assert_not_called()
        with self.subTest(parallel=True):
            with mock.patch.object(market.Portfolio, "is_worker_thread") as is_worker,\
                    mock.patch.object(market.Portfolio, "notify_and_wait") as notify:
                with self.subTest(worker=True):
                    market.Portfolio.data = store.LockedVar(None)
                    market.Portfolio.worker = mock.Mock()
                    is_worker.return_value = True
                    self.wm.get(market.Portfolio.URL, [
                        {"text":'{ "foo": "bar" }', "status_code": 200},
                        ])
                    market.Portfolio.get_cryptoportfolio()
                    self.assertIn("foo", market.Portfolio.data.get())
                parse_cryptoportfolio.reset_mock()
                with self.subTest(worker=False):
                    market.Portfolio.data = store.LockedVar(None)
                    market.Portfolio.worker = mock.Mock()
                    is_worker.return_value = False
                    market.Portfolio.get_cryptoportfolio()
                    notify.assert_called_once_with()
                    parse_cryptoportfolio.assert_not_called()

    def test_parse_cryptoportfolio(self):
        with self.subTest(description="Normal case"):
            market.Portfolio.data = store.LockedVar(store.json.loads(
                self.json_response, parse_int=D, parse_float=D))
            market.Portfolio.parse_cryptoportfolio()

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

            liquidities = market.Portfolio.liquidities.get()
            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), market.Portfolio.last_date.get())

        with self.subTest(description="Missing weight"):
            data = store.json.loads(self.json_response, parse_int=D, parse_float=D)
            del(data["portfolio_2"]["weights"])
            market.Portfolio.data = store.LockedVar(data)

            market.Portfolio.parse_cryptoportfolio()
            self.assertListEqual(
                    ["medium", "high"],
                    list(market.Portfolio.liquidities.get().keys()))
            self.assertEqual({}, market.Portfolio.liquidities.get("medium"))

        with self.subTest(description="All missing weights"):
            data = store.json.loads(self.json_response, parse_int=D, parse_float=D)
            del(data["portfolio_1"]["weights"])
            del(data["portfolio_2"]["weights"])
            market.Portfolio.data = store.LockedVar(data)

            market.Portfolio.parse_cryptoportfolio()
            self.assertEqual({}, market.Portfolio.liquidities.get("medium"))
            self.assertEqual({}, market.Portfolio.liquidities.get("high"))
            self.assertEqual(datetime.datetime(1,1,1), market.Portfolio.last_date.get())


    @mock.patch.object(market.Portfolio, "get_cryptoportfolio")
    def test_repartition(self, get_cryptoportfolio):
        market.Portfolio.liquidities = store.LockedVar({
                "medium": {
                    "2018-03-01": "medium_2018-03-01",
                    "2018-03-08": "medium_2018-03-08",
                    },
                "high": {
                    "2018-03-01": "high_2018-03-01",
                    "2018-03-08": "high_2018-03-08",
                    }
                })
        market.Portfolio.last_date = store.LockedVar("2018-03-08")

        self.assertEqual("medium_2018-03-08", market.Portfolio.repartition())
        get_cryptoportfolio.assert_called_once_with()
        self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(liquidity="medium"))
        self.assertEqual("high_2018-03-08", market.Portfolio.repartition(liquidity="high"))

    @mock.patch.object(market.time, "sleep")
    @mock.patch.object(market.Portfolio, "get_cryptoportfolio")
    def test_wait_for_recent(self, get_cryptoportfolio, sleep):
        self.call_count = 0
        def _get(refetch=False):
            if self.call_count != 0:
                self.assertTrue(refetch)
            else:
                self.assertFalse(refetch)
            self.call_count += 1
            market.Portfolio.last_date = store.LockedVar(store.datetime.now()\
                - store.timedelta(10)\
                + store.timedelta(self.call_count))
        get_cryptoportfolio.side_effect = _get

        market.Portfolio.wait_for_recent()
        sleep.assert_called_with(30)
        self.assertEqual(6, sleep.call_count)
        self.assertEqual(7, get_cryptoportfolio.call_count)
        market.Portfolio.report.print_log.assert_called_with("Attempt to fetch up-to-date cryptoportfolio")

        sleep.reset_mock()
        get_cryptoportfolio.reset_mock()
        market.Portfolio.last_date = store.LockedVar(None)
        self.call_count = 0
        market.Portfolio.wait_for_recent(delta=15)
        sleep.assert_not_called()
        self.assertEqual(1, get_cryptoportfolio.call_count)

        sleep.reset_mock()
        get_cryptoportfolio.reset_mock()
        market.Portfolio.last_date = store.LockedVar(None)
        self.call_count = 0
        market.Portfolio.wait_for_recent(delta=1)
        sleep.assert_called_with(30)
        self.assertEqual(9, sleep.call_count)
        self.assertEqual(10, get_cryptoportfolio.call_count)

    def test_is_worker_thread(self):
        with self.subTest(worker=None):
            self.assertFalse(store.Portfolio.is_worker_thread())

        with self.subTest(worker="not self"),\
                mock.patch("threading.current_thread") as current_thread:
            current = mock.Mock()
            current_thread.return_value = current
            store.Portfolio.worker = mock.Mock()
            self.assertFalse(store.Portfolio.is_worker_thread())

        with self.subTest(worker="self"),\
                mock.patch("threading.current_thread") as current_thread:
            current = mock.Mock()
            current_thread.return_value = current
            store.Portfolio.worker = current
            self.assertTrue(store.Portfolio.is_worker_thread())

    def test_start_worker(self):
        with mock.patch.object(store.Portfolio, "wait_for_notification") as notification:
            store.Portfolio.start_worker()
            notification.assert_called_once_with(poll=30)

            self.assertEqual("lock", store.Portfolio.last_date.lock.__class__.__name__)
            self.assertEqual("lock", store.Portfolio.liquidities.lock.__class__.__name__)
            store.Portfolio.report.start_lock.assert_called_once_with()

            self.assertIsNotNone(store.Portfolio.worker)
            self.assertIsNotNone(store.Portfolio.worker_notify)
            self.assertIsNotNone(store.Portfolio.callback)
            self.assertTrue(store.Portfolio.worker_started)

@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", self.m))

        with self.subTest(desc="no ticker for currency"):
            self.m.get_ticker.return_value = None

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

        with self.subTest(desc="nominal case"):
            self.m.get_ticker.return_value = {
                    "bid": D("0.2"),
                    "ask": D("0.4"),
                    "average": D("0.3"),
                    "foo": "bar",
                    }
            converted_amount = amount.in_currency("ETH", self.m)

            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", self.m, action="bid", compute_value="default")
            self.assertEqual(D("2"), converted_amount.value)

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

        converted_amount = amount.in_currency("ETH", self.m, 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"), 0-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_available": "0",
            "margin_in_position": "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_available.value)
        self.assertEqual("BTC", balance.margin_total.currency)
        self.assertEqual("BTC", balance.margin_borrowed.currency)
        self.assertEqual("BTC", balance.margin_available.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_in_position": 1, "margin_available": 2 })
        self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))

        balance = portfolio.Balance("BTX", { "margin_total": 2, "margin_available": 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_in_position": 1, "exchange_free": 2, "exchange_total": 2})
        self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [❌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_available"])
        self.assertEqual(D(0), as_json["margin_borrowed"])

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

        self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)

    def test_values(self):
        m = market.Market(self.ccxt, self.market_args())

        self.assertEqual(self.ccxt, m.ccxt)
        self.assertFalse(m.debug)
        self.assertIsInstance(m.report, market.ReportStore)
        self.assertIsInstance(m.trades, market.TradeStore)
        self.assertIsInstance(m.balances, market.BalanceStore)
        self.assertEqual(m, m.report.market)
        self.assertEqual(m, m.trades.market)
        self.assertEqual(m, m.balances.market)
        self.assertEqual(m, m.ccxt._market)

        m = market.Market(self.ccxt, self.market_args(debug=True))
        self.assertTrue(m.debug)

        m = market.Market(self.ccxt, self.market_args(debug=False))
        self.assertFalse(m.debug)

        with mock.patch("market.ReportStore") as report_store:
            with self.subTest(quiet=False):
                m = market.Market(self.ccxt, self.market_args(quiet=False))
                report_store.assert_called_with(m, verbose_print=True)
                report_store().log_market.assert_called_once()
            report_store.reset_mock()
            with self.subTest(quiet=True):
                m = market.Market(self.ccxt, self.market_args(quiet=True))
                report_store.assert_called_with(m, verbose_print=False)
                report_store().log_market.assert_called_once()

    @mock.patch("market.ccxt")
    def test_from_config(self, ccxt):
        with mock.patch("market.ReportStore"):
            ccxt.poloniexE.return_value = self.ccxt

            m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args())

            self.assertEqual(self.ccxt, m.ccxt)

        m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args(debug=True))
        self.assertEqual(True, m.debug)

    def test_get_tickers(self):
        self.ccxt.fetch_tickers.side_effect = [
                "tickers",
                market.NotSupported
                ]

        m = market.Market(self.ccxt, self.market_args())
        self.assertEqual("tickers", m.get_tickers())
        self.assertEqual("tickers", m.get_tickers())
        self.ccxt.fetch_tickers.assert_called_once()

        self.assertIsNone(m.get_tickers(refresh=self.time.time()))

    def test_get_ticker(self):
        with self.subTest(get_tickers=True):
            self.ccxt.fetch_tickers.return_value = {
                    "ETH/ETC": { "bid": 1, "ask": 3 },
                    "XVG/ETH": { "bid": 10, "ask": 40 },
                    }
            m = market.Market(self.ccxt, self.market_args())

            ticker = m.get_ticker("ETH", "ETC")
            self.assertEqual(1, ticker["bid"])
            self.assertEqual(3, ticker["ask"])
            self.assertEqual(2, ticker["average"])
            self.assertFalse(ticker["inverted"])

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

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

        with self.subTest(get_tickers=False):
            self.ccxt.fetch_tickers.return_value = None
            self.ccxt.fetch_ticker.side_effect = [
                    { "bid": 1, "ask": 3 },
                    market.ExchangeError("foo"),
                    { "bid": 10, "ask": 40 },
                    market.ExchangeError("foo"),
                    market.ExchangeError("foo"),
                    ]

            m = market.Market(self.ccxt, self.market_args())

            ticker = m.get_ticker("ETH", "ETC")
            self.ccxt.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 = m.get_ticker("ETH", "XVG")
            self.assertEqual(0.0625, ticker["average"])
            self.assertTrue(ticker["inverted"])
            self.assertIn("original", ticker)
            self.assertEqual(10, ticker["original"]["bid"])
            self.assertEqual(25, ticker["original"]["average"])

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

    def test_fetch_fees(self):
        m = market.Market(self.ccxt, self.market_args())
        self.ccxt.fetch_fees.return_value = "Foo"
        self.assertEqual("Foo", m.fetch_fees())
        self.ccxt.fetch_fees.assert_called_once()
        self.ccxt.reset_mock()
        self.assertEqual("Foo", m.fetch_fees())
        self.ccxt.fetch_fees.assert_not_called()

    @mock.patch.object(market.Portfolio, "repartition")
    @mock.patch.object(market.Market, "get_ticker")
    @mock.patch.object(market.TradeStore, "compute_trades")
    def test_prepare_trades(self, compute_trades, get_ticker, repartition):
        repartition.return_value = {
                "XEM": (D("0.75"), "long"),
                "BTC": (D("0.25"), "long"),
                }
        def _get_ticker(c1, c2):
            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

        with mock.patch("market.ReportStore"):
            m = market.Market(self.ccxt, self.market_args())
            self.ccxt.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")
                        },
                    }

            m.balances.fetch_balances(tag="tag")

            m.prepare_trades()
            compute_trades.assert_called()

            call = compute_trades.call_args
            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)
            m.report.log_stage.assert_called_once_with("prepare_trades",
                    base_currency='BTC', compute_value='average',
                    liquidity='medium', only=None, repartition=None)
            m.report.log_balances.assert_called_once_with(tag="tag")


    @mock.patch.object(market.time, "sleep")
    @mock.patch.object(market.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("market.ReportStore"):
                m = market.Market(self.ccxt, self.market_args(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()

                m.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)
                m.report.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"),
                        ]
                m.report.log_stage.assert_has_calls(calls)
                m.report.log_orders.assert_called()
                self.assertEqual(3, m.report.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),
                        ]
                m.report.log_orders.assert_has_calls(calls)
                calls = [
                        mock.call(order_mock1, 3, finished=True),
                        mock.call(order_mock3, 3, finished=True),
                        ]
                m.report.log_order.assert_has_calls(calls)

                if sleep is None:
                    if debug:
                        m.report.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)

        with self.subTest("disappearing order"), \
                mock.patch("market.ReportStore"):
            all_orders.reset_mock()
            m = market.Market(self.ccxt, self.market_args())

            order_mock1 = mock.Mock()
            order_mock2 = mock.Mock()
            all_orders.side_effect = [
                    [order_mock1, order_mock2],
                    [order_mock1, order_mock2],

                    [order_mock1, order_mock2],
                    [order_mock1, order_mock2],

                    []
                    ]

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

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

            trade_mock.tick_actions_recreate.return_value = "tick1"

            m.follow_orders()

            trade_mock.tick_actions_recreate.assert_called_once_with(2)
            trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
            m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)

    @mock.patch.object(market.BalanceStore, "fetch_balances")
    def test_move_balance(self, fetch_balances):
        for debug in [True, False]:
            with self.subTest(debug=debug),\
                    mock.patch("market.ReportStore"):
                m = market.Market(self.ccxt, self.market_args(debug=debug))

                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", m)

                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", m)

                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", m)

                m.trades.all = [trade1, trade2, trade3]
                balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
                balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
                balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
                m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}

                m.move_balances()

                fetch_balances.assert_called_with()
                m.report.log_move_balances.assert_called_once()

                if debug:
                    m.report.log_debug_action.assert_called()
                    self.assertEqual(3, m.report.log_debug_action.call_count)
                else:
                    self.ccxt.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
                    self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
                    self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")

        m.report.reset_mock()
        fetch_balances.reset_mock()
        with self.subTest(retry=True):
            with mock.patch("market.ReportStore"):
                m = market.Market(self.ccxt, self.market_args())

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

                m.trades.all = [trade]
                balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
                m.balances.all = {"BTC": balance}

                m.ccxt.transfer_balance.side_effect = [
                        market.ccxt.RequestTimeout,
                        market.ccxt.InvalidNonce,
                        True
                        ]
                m.move_balances()
                self.ccxt.transfer_balance.assert_has_calls([
                    mock.call("BTC", 3, "exchange", "margin"),
                    mock.call("BTC", 3, "exchange", "margin"),
                    mock.call("BTC", 3, "exchange", "margin")
                    ])
                self.assertEqual(3, fetch_balances.call_count)
                m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
                self.assertEqual(3, m.report.log_move_balances.call_count)

        self.ccxt.transfer_balance.reset_mock()
        m.report.reset_mock()
        fetch_balances.reset_mock()
        with self.subTest(retry=True, too_much=True):
            with mock.patch("market.ReportStore"):
                m = market.Market(self.ccxt, self.market_args())

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

                m.trades.all = [trade]
                balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
                m.balances.all = {"BTC": balance}

                m.ccxt.transfer_balance.side_effect = [
                        market.ccxt.RequestTimeout,
                        market.ccxt.RequestTimeout,
                        market.ccxt.RequestTimeout,
                        market.ccxt.RequestTimeout,
                        market.ccxt.RequestTimeout,
                        ]
                with self.assertRaises(market.ccxt.RequestTimeout):
                    m.move_balances()

        self.ccxt.transfer_balance.reset_mock()
        m.report.reset_mock()
        fetch_balances.reset_mock()
        with self.subTest(retry=True, partial_result=True):
            with mock.patch("market.ReportStore"):
                m = market.Market(self.ccxt, self.market_args())

                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", m)

                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", m)

                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", m)

                m.trades.all = [trade1, trade2, trade3]
                balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
                balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
                balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
                m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}

                call_counts = { "BTC": 0, "USDT": 0, "ETC": 0 }
                def _transfer_balance(currency, amount, from_, to_):
                    call_counts[currency] += 1
                    if currency == "BTC":
                        m.balances.all["BTC"] = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" })
                    if currency == "USDT":
                        if call_counts["USDT"] == 1:
                            raise market.ccxt.RequestTimeout
                        else:
                            m.balances.all["USDT"] = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" })
                    if currency == "ETC":
                            m.balances.all["ETC"] = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" })


                m.ccxt.transfer_balance.side_effect = _transfer_balance

                m.move_balances()
                self.ccxt.transfer_balance.assert_has_calls([
                    mock.call("BTC", 3, "exchange", "margin"),
                    mock.call('USDT', 100, 'exchange', 'margin'),
                    mock.call('USDT', 100, 'exchange', 'margin'),
                    mock.call("ETC", 5, "margin", "exchange")
                    ])
                self.assertEqual(2, fetch_balances.call_count)
                m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
                self.assertEqual(2, m.report.log_move_balances.call_count)
                m.report.log_move_balances.asser_has_calls([
                    mock.call(
                        {
                            'BTC': portfolio.Amount("BTC", "3"),
                            'USDT': portfolio.Amount("USDT", "150"),
                            'ETC': portfolio.Amount("ETC", "10"),
                            },
                        {
                            'BTC': portfolio.Amount("BTC", "3"),
                            'USDT': portfolio.Amount("USDT", "100"),
                            }),
                    mock.call(
                        {
                            'BTC': portfolio.Amount("BTC", "3"),
                            'USDT': portfolio.Amount("USDT", "150"),
                            'ETC': portfolio.Amount("ETC", "10"),
                            },
                        {
                            'BTC': portfolio.Amount("BTC", "0"),
                            'USDT': portfolio.Amount("USDT", "100"),
                            'ETC': portfolio.Amount("ETC", "-5"),
                            }),
                    ])


    def test_store_file_report(self):
        file_open = mock.mock_open()
        m = market.Market(self.ccxt,
                self.market_args(report_path="present"), user_id=1)
        with self.subTest(file="present"),\
                mock.patch("market.open", file_open),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(market, "datetime") as time_mock:

            report.print_logs = [[time_mock.now(), "Foo"], [time_mock.now(), "Bar"]]
            report.to_json.return_value = "json_content"

            m.store_file_report(datetime.datetime(2018, 2, 25))

            file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
            file_open.assert_any_call("present/2018-02-25T00:00:00_1.log", "w")
            file_open().write.assert_any_call("json_content")
            file_open().write.assert_any_call("Foo\nBar")
            m.report.to_json.assert_called_once_with()

        m = market.Market(self.ccxt, self.market_args(report_path="error"), user_id=1)
        with self.subTest(file="error"),\
                mock.patch("market.open") as file_open,\
                mock.patch.object(m, "report") as report,\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            file_open.side_effect = FileNotFoundError

            m.store_file_report(datetime.datetime(2018, 2, 25))

            self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")

    @mock.patch.object(market, "psycopg2")
    def test_store_database_report(self, psycopg2):
        connect_mock = mock.Mock()
        cursor_mock = mock.MagicMock()

        connect_mock.cursor.return_value = cursor_mock
        psycopg2.connect.return_value = connect_mock
        m = market.Market(self.ccxt, self.market_args(),
                pg_config={"config": "pg_config"}, user_id=1)
        cursor_mock.fetchone.return_value = [42]

        with self.subTest(error=False),\
                mock.patch.object(m, "report") as report:
            report.to_json_array.return_value = [
                    ("date1", "type1", "payload1"),
                    ("date2", "type2", "payload2"),
                    ]
            m.store_database_report(datetime.datetime(2018, 3, 24))
            connect_mock.assert_has_calls([
                mock.call.cursor(),
                mock.call.cursor().execute('INSERT INTO reports("date", "market_config_id", "debug") VALUES (%s, %s, %s) RETURNING id;', (datetime.datetime(2018, 3, 24), None, False)),
                mock.call.cursor().fetchone(),
                mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date1', 42, 'type1', 'payload1')),
                mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date2', 42, 'type2', 'payload2')),
                mock.call.commit(),
                mock.call.cursor().close(),
                mock.call.close()
                ])

        connect_mock.reset_mock()
        with self.subTest(error=True),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            psycopg2.connect.side_effect = Exception("Bouh")
            m.store_database_report(datetime.datetime(2018, 3, 24))
            self.assertEqual(stdout_mock.getvalue(), "impossible to store report to database: Exception; Bouh\n")

    def test_store_report(self):
        m = market.Market(self.ccxt, self.market_args(report_db=False), user_id=1)
        with self.subTest(file=None, pg_config=None),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(m, "store_database_report") as db_report,\
                mock.patch.object(m, "store_file_report") as file_report:
            m.store_report()
            report.merge.assert_called_with(store.Portfolio.report)

            file_report.assert_not_called()
            db_report.assert_not_called()

        report.reset_mock()
        m = market.Market(self.ccxt, self.market_args(report_db=False, report_path="present"), user_id=1)
        with self.subTest(file="present", pg_config=None),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(m, "store_file_report") as file_report,\
                mock.patch.object(m, "store_database_report") as db_report,\
                mock.patch.object(market, "datetime") as time_mock:

            time_mock.now.return_value = datetime.datetime(2018, 2, 25)

            m.store_report()

            report.merge.assert_called_with(store.Portfolio.report)
            file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
            db_report.assert_not_called()

        report.reset_mock()
        m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"), user_id=1)
        with self.subTest(file="present", pg_config=None, report_db=True),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(m, "store_file_report") as file_report,\
                mock.patch.object(m, "store_database_report") as db_report,\
                mock.patch.object(market, "datetime") as time_mock:

            time_mock.now.return_value = datetime.datetime(2018, 2, 25)

            m.store_report()

            report.merge.assert_called_with(store.Portfolio.report)
            file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
            db_report.assert_not_called()

        report.reset_mock()
        m = market.Market(self.ccxt, self.market_args(report_db=True), pg_config="present", user_id=1)
        with self.subTest(file=None, pg_config="present"),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(m, "store_file_report") as file_report,\
                mock.patch.object(m, "store_database_report") as db_report,\
                mock.patch.object(market, "datetime") as time_mock:

            time_mock.now.return_value = datetime.datetime(2018, 2, 25)

            m.store_report()

            report.merge.assert_called_with(store.Portfolio.report)
            file_report.assert_not_called()
            db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))

        report.reset_mock()
        m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"),
                pg_config="pg_config", user_id=1)
        with self.subTest(file="present", pg_config="present"),\
                mock.patch.object(m, "report") as report,\
                mock.patch.object(m, "store_file_report") as file_report,\
                mock.patch.object(m, "store_database_report") as db_report,\
                mock.patch.object(market, "datetime") as time_mock:

            time_mock.now.return_value = datetime.datetime(2018, 2, 25)

            m.store_report()

            report.merge.assert_called_with(store.Portfolio.report)
            file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
            db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))

    def test_print_orders(self):
        m = market.Market(self.ccxt, self.market_args())
        with mock.patch.object(m.report, "log_stage") as log_stage,\
                mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
                mock.patch.object(m, "prepare_trades") as prepare_trades,\
                mock.patch.object(m.trades, "prepare_orders") as prepare_orders:
            m.print_orders()

            log_stage.assert_called_with("print_orders")
            fetch_balances.assert_called_with(tag="print_orders")
            prepare_trades.assert_called_with(base_currency="BTC",
                    compute_value="average")
            prepare_orders.assert_called_with(compute_value="average")

    def test_print_balances(self):
        m = market.Market(self.ccxt, self.market_args())

        with mock.patch.object(m.balances, "in_currency") as in_currency,\
                mock.patch.object(m.report, "log_stage") as log_stage,\
                mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
                mock.patch.object(m.report, "print_log") as print_log:

            in_currency.return_value = {
                    "BTC": portfolio.Amount("BTC", "0.65"),
                    "ETH": portfolio.Amount("BTC", "0.3"),
                    }

            m.print_balances()

            log_stage.assert_called_once_with("print_balances")
            fetch_balances.assert_called_with()
            print_log.assert_has_calls([
                mock.call("total:"),
                mock.call(portfolio.Amount("BTC", "0.95")),
                ])

    @mock.patch("market.Processor.process")
    @mock.patch("market.ReportStore.log_error")
    @mock.patch("market.Market.store_report")
    def test_process(self, store_report, log_error, process):
        m = market.Market(self.ccxt, self.market_args())
        with self.subTest(before=False, after=False):
            m.process(None)

            process.assert_not_called()
            store_report.assert_called_once()
            log_error.assert_not_called()

        process.reset_mock()
        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(before=True, after=False):
            m.process(None, before=True)

            process.assert_called_once_with("sell_all", steps="before")
            store_report.assert_called_once()
            log_error.assert_not_called()

        process.reset_mock()
        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(before=False, after=True):
            m.process(None, after=True)

            process.assert_called_once_with("sell_all", steps="after")
            store_report.assert_called_once()
            log_error.assert_not_called()

        process.reset_mock()
        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(before=True, after=True):
            m.process(None, before=True, after=True)

            process.assert_has_calls([
                mock.call("sell_all", steps="before"),
                mock.call("sell_all", steps="after"),
                ])
            store_report.assert_called_once()
            log_error.assert_not_called()

        process.reset_mock()
        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(action="print_balances"),\
                mock.patch.object(m, "print_balances") as print_balances:
            m.process(["print_balances"])

            process.assert_not_called()
            log_error.assert_not_called()
            store_report.assert_called_once()
            print_balances.assert_called_once_with()

        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(action="print_orders"),\
                mock.patch.object(m, "print_orders") as print_orders,\
                mock.patch.object(m, "print_balances") as print_balances:
            m.process(["print_orders", "print_balances"])

            process.assert_not_called()
            log_error.assert_not_called()
            store_report.assert_called_once()
            print_orders.assert_called_once_with()
            print_balances.assert_called_once_with()

        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(action="unknown"):
            m.process(["unknown"])
            log_error.assert_called_once_with("market_process", message="Unknown action unknown")
            store_report.assert_called_once()

        log_error.reset_mock()
        store_report.reset_mock()
        with self.subTest(unhandled_exception=True):
            process.side_effect = Exception("bouh")

            m.process(None, before=True)
            log_error.assert_called_with("market_process", exception=mock.ANY)
            store_report.assert_called_once()
 
@unittest.skipUnless("unit" in limits, "Unit skipped")
class TradeStoreTest(WebMockTestCase):
    def test_compute_trades(self):
        self.m.balances.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)
                ]

        with mock.patch.object(market.TradeStore, "trade_if_matching") as trade_if_matching:
            trade_store = market.TradeStore(self.m)
            trade_if_matching.side_effect = side_effect

            trade_store.compute_trades(values_in_base,
                    new_repartition, only="only")

            self.assertEqual(5, trade_if_matching.call_count)
            self.assertEqual(3, len(trade_store.all))
            self.assertEqual([1, 4, 5], trade_store.all)
            self.m.report.log_trades.assert_called_with(side_effect, "only")

    def test_trade_if_matching(self):

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

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

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

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

    def test_prepare_orders(self):
        trade_store = market.TradeStore(self.m)

        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()
        trade_mock3 = mock.Mock()

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

        trade_mock1.pending = True
        trade_mock2.pending = True
        trade_mock3.pending = False

        trade_store.all.append(trade_mock1)
        trade_store.all.append(trade_mock2)
        trade_store.all.append(trade_mock3)

        trade_store.prepare_orders()
        trade_mock1.prepare_order.assert_called_with(compute_value="default")
        trade_mock2.prepare_order.assert_called_with(compute_value="default")
        trade_mock3.prepare_order.assert_not_called()
        self.m.report.log_orders.assert_called_once_with([1, 2], None, "default")

        self.m.report.log_orders.reset_mock()

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

        trade_mock1.prepare_order.reset_mock()
        trade_mock2.prepare_order.reset_mock()
        self.m.report.log_orders.reset_mock()

        trade_mock1.action = "foo"
        trade_mock2.action = "bar"
        trade_store.prepare_orders(only="bar")
        trade_mock1.prepare_order.assert_not_called()
        trade_mock2.prepare_order.assert_called_with(compute_value="default")
        self.m.report.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()
        trade_store = market.TradeStore(self.m)
        trade_store.all = [trade_mock1, trade_mock2, trade_mock3]

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

    def test_run_orders(self):
        with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
            order_mock1 = mock.Mock()
            order_mock2 = mock.Mock()
            order_mock3 = mock.Mock()
            trade_store = market.TradeStore(self.m)

            all_orders.return_value = [order_mock1, order_mock2, order_mock3]

            trade_store.run_orders()

            all_orders.assert_called_with(state="pending")

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

        self.m.report.log_stage.assert_called_with("run_orders")
        self.m.report.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"

        trade_store = market.TradeStore(self.m)
        trade_store.all.append(trade_mock1)
        trade_store.all.append(trade_mock2)

        orders = trade_store.all_orders()
        self.assertEqual(3, len(orders))

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

    def test_update_all_orders_status(self):
        with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
            order_mock1 = mock.Mock()
            order_mock2 = mock.Mock()
            order_mock3 = mock.Mock()

            all_orders.return_value = [order_mock1, order_mock2, order_mock3]
            
            trade_store = market.TradeStore(self.m)

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

    def test_close_trades(self):
        trade_mock1 = mock.Mock()
        trade_mock2 = mock.Mock()
        trade_mock3 = mock.Mock()

        trade_store = market.TradeStore(self.m)

        trade_store.all.append(trade_mock1)
        trade_store.all.append(trade_mock2)
        trade_store.all.append(trade_mock3)

        trade_store.close_trades()

        trade_mock1.close.assert_called_once_with()
        trade_mock2.close.assert_called_once_with()
        trade_mock3.close.assert_called_once_with()

    def test_pending(self):
        trade_mock1 = mock.Mock()
        trade_mock1.pending = True
        trade_mock2 = mock.Mock()
        trade_mock2.pending = True
        trade_mock3 = mock.Mock()
        trade_mock3.pending = False

        trade_store = market.TradeStore(self.m)

        trade_store.all.append(trade_mock1)
        trade_store.all.append(trade_mock2)
        trade_store.all.append(trade_mock3)

        self.assertEqual([trade_mock1, trade_mock2], trade_store.pending)

@unittest.skipUnless("unit" in limits, "Unit skipped")
class BalanceStoreTest(WebMockTestCase):
    def setUp(self):
        super().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,
                    },
                }

    def test_in_currency(self):
        self.m.get_ticker.return_value = {
                "bid": D("0.09"),
                "ask": D("0.11"),
                "average": D("0.1"),
                }

        balance_store = market.BalanceStore(self.m)
        balance_store.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}),
                }

        amounts = balance_store.in_currency("BTC")
        self.assertEqual("BTC", amounts["ETH"].currency)
        self.assertEqual(D("0.65"), amounts["BTC"].value)
        self.assertEqual(D("0.30"), amounts["ETH"].value)
        self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
                "average", "total")
        self.m.report.log_tickers.reset_mock()

        amounts = balance_store.in_currency("BTC", compute_value="bid")
        self.assertEqual(D("0.65"), amounts["BTC"].value)
        self.assertEqual(D("0.27"), amounts["ETH"].value)
        self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
                "bid", "total")
        self.m.report.log_tickers.reset_mock()

        amounts = balance_store.in_currency("BTC", compute_value="bid", type="exchange_used")
        self.assertEqual(D("0.30"), amounts["BTC"].value)
        self.assertEqual(0, amounts["ETH"].value)
        self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
                "bid", "exchange_used")
        self.m.report.log_tickers.reset_mock()

    def test_fetch_balances(self):
        self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance

        balance_store = market.BalanceStore(self.m)

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

        balance_store.all["ETC"] = portfolio.Balance("ETC", {
            "exchange_total": "1", "exchange_free": "0",
            "exchange_used": "1" })
        balance_store.fetch_balances(tag="foo")
        self.assertEqual(0, balance_store.all["ETC"].total)
        self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies()))
        self.m.report.log_balances.assert_called_with(tag="foo")

    @mock.patch.object(market.Portfolio, "repartition")
    def test_dispatch_assets(self, repartition):
        self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance

        balance_store = market.BalanceStore(self.m)
        balance_store.fetch_balances()

        self.assertNotIn("XEM", balance_store.currencies())

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

        amounts = balance_store.dispatch_assets(portfolio.Amount("BTC", "11.1"))
        repartition.assert_called_with(liquidity="medium")
        self.assertIn("XEM", balance_store.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)
        self.m.report.log_balances.assert_called_with(tag=None)
        self.m.report.log_dispatch.assert_called_once_with(portfolio.Amount("BTC",
            "11.1"), amounts, "medium", repartition_hash)

    def test_currencies(self):
        balance_store = market.BalanceStore(self.m)

        balance_store.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(balance_store.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

        balance_store = market.BalanceStore(self.m)
        balance_store.all = {
                "BTC": balance_mock1,
                "ETH": balance_mock2,
                }

        as_json = balance_store.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.m)
        self.assertEqual("BTC", trade.base_currency)
        self.assertEqual("ETH", trade.currency)
        self.assertEqual(self.m, trade.market)

        with self.assertRaises(AssertionError):
            portfolio.Trade(value_from, -value_to, "ETH", self.m)
        with self.assertRaises(AssertionError):
            portfolio.Trade(value_from, value_to, "ETC", self.m)
        with self.assertRaises(AssertionError):
            value_from.currency = "ETH"
            portfolio.Trade(value_from, value_to, "ETH", self.m)
            value_from.currency = "BTC"
        with self.assertRaises(AssertionError):
            value_from2 = portfolio.Amount("BTC", "1.0")
            portfolio.Trade(value_from2, value_to, "ETH", self.m)

        value_from = portfolio.Amount("BTC", 0)
        trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
        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.m)

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

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

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

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

        trade.inverted = False
        self.assertEqual("buy", trade.order_action())
        trade.inverted = True
        self.assertEqual("sell", trade.order_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.m)

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

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

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

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

    def test_is_fullfiled(self):
        with self.subTest(inverted=False):
            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.m)

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

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

            self.assertFalse(trade.is_fullfiled)

            order3 = mock.Mock()
            order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
            trade.orders.append(order3)

            self.assertTrue(trade.is_fullfiled)

            order1.filled_amount.assert_called_with(in_base_currency=True)
            order2.filled_amount.assert_called_with(in_base_currency=True)
            order3.filled_amount.assert_called_with(in_base_currency=True)

        with self.subTest(inverted=True):
            value_from = portfolio.Amount("BTC", "0.5")
            value_from.linked_to = portfolio.Amount("USDT", "1000.0")
            value_to = portfolio.Amount("BTC", "1.0")
            trade = portfolio.Trade(value_from, value_to, "USDT", self.m)
            trade.inverted = True

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

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

            self.assertFalse(trade.is_fullfiled)

            order3 = mock.Mock()
            order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
            trade.orders.append(order3)

            self.assertTrue(trade.is_fullfiled)

            order1.filled_amount.assert_called_with(in_base_currency=False)
            order2.filled_amount.assert_called_with(in_base_currency=False)
            order3.filled_amount.assert_called_with(in_base_currency=False)


    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", self.m)

        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(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):
        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", self.m)

            trade.prepare_order()

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

        self.m.get_ticker.return_value = { "inverted": False }
        with self.subTest(desc="Already filled"):
            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", self.m)

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
            self.assertEqual(0, len(trade.orders))
            self.m.report.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", self.m)

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(self.m.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", self.m,
                    trade, close_if_possible=False)

        with self.subTest(action="dispose", inverted=False, close_if_possible=True):
            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", self.m)

            trade.prepare_order(close_if_possible=True)

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(self.m.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", self.m,
                    trade, close_if_possible=True)

        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", self.m)

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=True)
            compute_value.assert_called_with(self.m.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", self.m,
                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", self.m)

            trade.prepare_order()

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(self.m.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", self.m,
                    trade, close_if_possible=True)

        self.m.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", self.m)

            trade.prepare_order(compute_value="foo")

            filled_amount.assert_called_with(in_base_currency=True)
            compute_value.assert_called_with(self.m.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", self.m,
                    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", self.m)

            trade.prepare_order(compute_value="foo")

            filled_amount.assert_called_with(in_base_currency=False)
            compute_value.assert_called_with(self.m.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", self.m,
                    trade, close_if_possible=False)

    def test_tick_actions_recreate(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.m)

        self.assertEqual("average", trade.tick_actions_recreate(0))
        self.assertEqual("foo", trade.tick_actions_recreate(0, default="foo"))
        self.assertEqual("average", trade.tick_actions_recreate(1))
        self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(2))
        self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(3))
        self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(5))
        self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(6))
        self.assertEqual("default", trade.tick_actions_recreate(7))
        self.assertEqual("default", trade.tick_actions_recreate(8))

    @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", self.m)
        prepare_order.return_value = new_order_mock

        for i in [0, 1, 3, 4, 6]:
            with self.subTest(tick=i):
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                self.m.report.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 = []
            self.m.report.log_order.reset_mock()

        trade.update_order(order_mock, 2)
        order_mock.cancel.assert_called()
        new_order_mock.run.assert_called()
        prepare_order.assert_called()
        self.m.report.log_order.assert_called()
        self.assertEqual(2, self.m.report.log_order.call_count)
        calls = [
                mock.call(order_mock, 2, update="adjusting",
                    compute_value=mock.ANY,
                    new_order=new_order_mock),
                mock.call(order_mock, 2, new_order=new_order_mock),
                ]
        self.m.report.log_order.assert_has_calls(calls)

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []
        self.m.report.log_order.reset_mock()

        trade.update_order(order_mock, 5)
        order_mock.cancel.assert_called()
        new_order_mock.run.assert_called()
        prepare_order.assert_called()
        self.assertEqual(2, self.m.report.log_order.call_count)
        self.m.report.log_order.assert_called()
        calls = [
                mock.call(order_mock, 5, update="adjusting",
                    compute_value=mock.ANY,
                    new_order=new_order_mock),
                mock.call(order_mock, 5, new_order=new_order_mock),
                ]
        self.m.report.log_order.assert_has_calls(calls)

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []
        self.m.report.log_order.reset_mock()

        trade.update_order(order_mock, 7)
        order_mock.cancel.assert_called()
        new_order_mock.run.assert_called()
        prepare_order.assert_called_with(compute_value="default")
        self.m.report.log_order.assert_called()
        self.assertEqual(2, self.m.report.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),
                ]
        self.m.report.log_order.assert_has_calls(calls)

        order_mock.reset_mock()
        new_order_mock.reset_mock()
        trade.orders = []
        self.m.report.log_order.reset_mock()

        for i in [10, 13, 16]:
            with self.subTest(tick=i):
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_called()
                new_order_mock.run.assert_called()
                prepare_order.assert_called_with(compute_value="default")
                self.m.report.log_order.assert_called()
                self.assertEqual(2, self.m.report.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),
                        ]
                self.m.report.log_order.assert_has_calls(calls)

            order_mock.reset_mock()
            new_order_mock.reset_mock()
            trade.orders = []
            self.m.report.log_order.reset_mock()

        for i in [8, 9, 11, 12]:
            with self.subTest(tick=i):
                trade.update_order(order_mock, i)
                order_mock.cancel.assert_not_called()
                new_order_mock.run.assert_not_called()
                self.m.report.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 = []
            self.m.report.log_order.reset_mock()


    def test_print_with_order(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.m)

        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)

        with mock.patch.object(trade, "filled_amount") as filled:
            filled.return_value = portfolio.Amount("BTC", "0.1")

            trade.print_with_order()

            self.m.report.print_log.assert_called()
            calls = self.m.report.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]))

            self.m.report.print_log.reset_mock()

            filled.return_value = portfolio.Amount("BTC", "0.5")
            trade.print_with_order()
            calls = self.m.report.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.m.report.print_log.reset_mock()

            filled.return_value = portfolio.Amount("BTC", "0.1")
            trade.closed = True
            trade.print_with_order()
            calls = self.m.report.print_log.mock_calls
            self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire ❌)", str(calls[0][1][0]))

    def test_close(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.m)
        order1 = mock.Mock()
        trade.orders.append(order1)

        trade.close()

        self.assertEqual(True, trade.closed)
        order1.cancel.assert_called_once_with()

    def test_pending(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.m)

        trade.closed = True
        self.assertEqual(False, trade.pending)

        trade.closed = False
        self.assertEqual(True, trade.pending)

        order1 = mock.Mock()
        order1.filled_amount.return_value = portfolio.Amount("BTC", "0.5")
        trade.orders.append(order1)
        self.assertEqual(False, trade.pending)

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

        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", self.m)

        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")
    def test_cancel(self, fetch):
        with self.subTest(debug=True):
            self.m.debug = True
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.status = "open"

            order.cancel()
            self.m.ccxt.cancel_order.assert_not_called()
            self.m.report.log_debug_action.assert_called_once()
            self.m.report.log_debug_action.reset_mock()
            self.assertEqual("canceled", order.status)

        with self.subTest(desc="Nominal case"):
            self.m.debug = False
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.status = "open"
            order.id = 42

            order.cancel()
            self.m.ccxt.cancel_order.assert_called_with(42)
            fetch.assert_called_once_with()
            self.m.report.log_debug_action.assert_not_called()

        with self.subTest(exception=True):
            self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.status = "open"
            order.id = 42
            order.cancel()
            self.m.ccxt.cancel_order.assert_called_with(42)
            self.m.report.log_error.assert_called_once()

        self.m.reset_mock()
        with self.subTest(id=None):
            self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.status = "open"
            order.cancel()
            self.m.ccxt.cancel_order.assert_not_called()

        self.m.reset_mock()
        with self.subTest(open=False):
            self.m.ccxt.cancel_order.side_effect = portfolio.OrderNotFound
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.status = "closed"
            order.cancel()
            self.m.ccxt.cancel_order.assert_not_called()

    def test_dust_amount_remaining(self):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", self.m, "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", self.m, "trade")

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

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

    @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", self.m, "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):
        self.m.ccxt.privatePostReturnOrderTrades.return_value = [
                {
                    "tradeID": 42, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 13:00:12", "rate": "0.1",
                    "amount": "3", "total": "0.3"
                    },
                {
                    "tradeID": 43, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 12:00:12", "rate": "0.2",
                    "amount": "2", "total": "0.4"
                    }
            ]
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", self.m, "trade")
        order.id = 12
        order.mouvements = ["Foo", "Bar", "Baz"]

        order.fetch_mouvements()

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

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

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

        order.mark_finished_order()
        self.m.ccxt.close_margin_position.assert_called_with("ETH", "BTC")
        self.m.ccxt.close_margin_position.reset_mock()

        order.status = "open"
        order.mark_finished_order()
        self.m.ccxt.close_margin_position.assert_not_called()

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

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

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

        self.m.debug = True

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

        order.mark_finished_order()
        self.m.ccxt.close_margin_position.assert_not_called()
        self.m.report.log_debug_action.assert_called_once()

    @mock.patch.object(portfolio.Order, "fetch_mouvements")
    @mock.patch.object(portfolio.Order, "mark_disappeared_order")
    @mock.patch.object(portfolio.Order, "mark_finished_order")
    def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", self.m, "trade")
        order.id = 45
        with self.subTest(debug=True):
            self.m.debug = True
            order.fetch()
            self.m.report.log_debug_action.assert_called_once()
            self.m.report.log_debug_action.reset_mock()
            self.m.ccxt.fetch_order.assert_not_called()
            mark_finished_order.assert_not_called()
            mark_disappeared_order.assert_not_called()
            fetch_mouvements.assert_not_called()

        with self.subTest(debug=False):
            self.m.debug = False
            self.m.ccxt.fetch_order.return_value = {
                    "status": "foo",
                    "datetime": "timestamp"
                    }
            order.fetch()

            self.m.ccxt.fetch_order.assert_called_once_with(45)
            fetch_mouvements.assert_called_once()
            self.assertEqual("foo", order.status)
            self.assertEqual("timestamp", order.timestamp)
            self.assertEqual(1, len(order.results))
            self.m.report.log_debug_action.assert_not_called()
            mark_finished_order.assert_called_once()
            mark_disappeared_order.assert_called_once()

            mark_finished_order.reset_mock()
            with self.subTest(missing_order=True):
                self.m.ccxt.fetch_order.side_effect = [
                        portfolio.OrderNotCached,
                        ]
                order.fetch()
                self.assertEqual("closed_unknown", order.status)
                mark_finished_order.assert_called_once()

    def test_mark_disappeared_order(self):
        with self.subTest("Open order"):
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.id = 45
            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
                "tradeID":21336541,
                "currencyPair":"BTC_XRP",
                "type":"sell",
                "rate":"0.00007013",
                "amount":"0.00000222",
                "total":"0.00000000",
                "fee":"0.00150000",
                "date":"2018-04-02 00:09:13"
                }))
            order.mark_disappeared_order()
            self.assertEqual("pending", order.status)

        with self.subTest("Non-zero amount"):
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.id = 45
            order.status = "closed"
            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
                "tradeID":21336541,
                "currencyPair":"BTC_XRP",
                "type":"sell",
                "rate":"0.00007013",
                "amount":"0.00000222",
                "total":"0.00000010",
                "fee":"0.00150000",
                "date":"2018-04-02 00:09:13"
                }))
            order.mark_disappeared_order()
            self.assertEqual("closed", order.status)

        with self.subTest("Other mouvements"):
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.id = 45
            order.status = "closed"
            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
                "tradeID":21336541,
                "currencyPair":"BTC_XRP",
                "type":"sell",
                "rate":"0.00007013",
                "amount":"0.00000222",
                "total":"0.00000001",
                "fee":"0.00150000",
                "date":"2018-04-02 00:09:13"
                }))
            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
                "tradeID":21336541,
                "currencyPair":"BTC_XRP",
                "type":"sell",
                "rate":"0.00007013",
                "amount":"0.00000222",
                "total":"0.00000000",
                "fee":"0.00150000",
                "date":"2018-04-02 00:09:13"
                }))
            order.mark_disappeared_order()
            self.assertEqual("error_disappeared", order.status)

        with self.subTest("Order disappeared"):
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.id = 45
            order.status = "closed"
            order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
                "tradeID":21336541,
                "currencyPair":"BTC_XRP",
                "type":"sell",
                "rate":"0.00007013",
                "amount":"0.00000222",
                "total":"0.00000000",
                "fee":"0.00150000",
                "date":"2018-04-02 00:09:13"
                }))
            order.mark_disappeared_order()
            self.assertEqual("error_disappeared", order.status)

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

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

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

    def test_run(self):
        self.m.ccxt.order_precision.return_value = 4
        with self.subTest(debug=True):
            self.m.debug = True
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.run()
            self.m.ccxt.create_order.assert_not_called()
            self.m.report.log_debug_action.assert_called_with("market.ccxt.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)

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

        self.m.ccxt.create_order.reset_mock()
        with self.subTest(exception=True):
            order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                    D("0.1"), "BTC", "long", self.m, "trade")
            self.m.ccxt.create_order.side_effect = Exception("bouh")
            order.run()
            self.m.ccxt.create_order.assert_called_once()
            self.assertEqual(0, len(order.results))
            self.assertEqual("error", order.status)
            self.m.report.log_error.assert_called_once()

        self.m.ccxt.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", self.m, "trade")
            self.m.ccxt.create_order.side_effect = portfolio.InvalidOrder
            order.run()
            self.m.ccxt.create_order.assert_called_once()
            self.assertEqual(0, len(order.results))
            self.assertEqual("closed", order.status)
            mark_finished_order.assert_called_once()

        self.m.ccxt.order_precision.return_value = 8
        self.m.ccxt.create_order.reset_mock()
        with self.subTest(insufficient_funds=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", self.m, "trade")
            self.m.ccxt.create_order.side_effect = [
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    { "id": 123 },
                    ]
            order.run()
            self.m.ccxt.create_order.assert_has_calls([
                mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
                ])
            self.assertEqual(4, self.m.ccxt.create_order.call_count)
            self.assertEqual(1, len(order.results))
            self.assertEqual("open", order.status)
            self.assertEqual(4, order.tries)
            self.m.report.log_error.assert_called()
            self.assertEqual(4, self.m.report.log_error.call_count)

        self.m.ccxt.order_precision.return_value = 8
        self.m.ccxt.create_order.reset_mock()
        self.m.report.log_error.reset_mock()
        with self.subTest(insufficient_funds=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", self.m, "trade")
            self.m.ccxt.create_order.side_effect = [
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    portfolio.InsufficientFunds,
                    ]
            order.run()
            self.m.ccxt.create_order.assert_has_calls([
                mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.00099'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.0009801'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.00097029'), account='exchange', price=D('0.1')),
                mock.call('ETH/BTC', 'limit', 'buy', D('0.00096059'), account='exchange', price=D('0.1')),
                ])
            self.assertEqual(5, self.m.ccxt.create_order.call_count)
            self.assertEqual(0, len(order.results))
            self.assertEqual("error", order.status)
            self.assertEqual(5, order.tries)
            self.m.report.log_error.assert_called()
            self.assertEqual(5, self.m.report.log_error.call_count)
            self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00096060 ETH at 0.1 BTC [pending])", exception=mock.ANY)

        self.m.reset_mock()
        with self.subTest(invalid_nonce=True):
            with self.subTest(retry_success=True):
                order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                        D("0.1"), "BTC", "long", self.m, "trade")
                self.m.ccxt.create_order.side_effect = [
                        portfolio.InvalidNonce,
                        portfolio.InvalidNonce,
                        { "id": 123 },
                        ]
                order.run()
                self.m.ccxt.create_order.assert_has_calls([
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                ])
                self.assertEqual(3, self.m.ccxt.create_order.call_count)
                self.assertEqual(3, order.tries)
                self.m.report.log_error.assert_called()
                self.assertEqual(2, self.m.report.log_error.call_count)
                self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after invalid nonce", exception=mock.ANY)
                self.assertEqual(123, order.id)

            self.m.reset_mock()
            with self.subTest(retry_success=False):
                order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                        D("0.1"), "BTC", "long", self.m, "trade")
                self.m.ccxt.create_order.side_effect = [
                        portfolio.InvalidNonce,
                        portfolio.InvalidNonce,
                        portfolio.InvalidNonce,
                        portfolio.InvalidNonce,
                        portfolio.InvalidNonce,
                        ]
                order.run()
                self.assertEqual(5, self.m.ccxt.create_order.call_count)
                self.assertEqual(5, order.tries)
                self.m.report.log_error.assert_called()
                self.assertEqual(5, self.m.report.log_error.call_count)
                self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after invalid nonce", exception=mock.ANY)
                self.assertEqual("error", order.status)

        self.m.reset_mock()
        with self.subTest(request_timeout=True):
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            with self.subTest(retrieved=False), \
                    mock.patch.object(order, "retrieve_order") as retrieve:
                self.m.ccxt.create_order.side_effect = [
                        portfolio.RequestTimeout,
                        portfolio.RequestTimeout,
                        { "id": 123 },
                        ]
                retrieve.return_value = False
                order.run()
                self.m.ccxt.create_order.assert_has_calls([
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                ])
                self.assertEqual(3, self.m.ccxt.create_order.call_count)
                self.assertEqual(3, order.tries)
                self.m.report.log_error.assert_called()
                self.assertEqual(2, self.m.report.log_error.call_count)
                self.m.report.log_error.assert_called_with(mock.ANY, message="Retrying after timeout", exception=mock.ANY)
                self.assertEqual(123, order.id)

            self.m.reset_mock()
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            with self.subTest(retrieved=True), \
                    mock.patch.object(order, "retrieve_order") as retrieve:
                self.m.ccxt.create_order.side_effect = [
                        portfolio.RequestTimeout,
                        ]
                def _retrieve():
                    order.results.append({"id": 123})
                    return True
                retrieve.side_effect = _retrieve
                order.run()
                self.m.ccxt.create_order.assert_has_calls([
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                ])
                self.assertEqual(1, self.m.ccxt.create_order.call_count)
                self.assertEqual(1, order.tries)
                self.m.report.log_error.assert_called()
                self.assertEqual(1, self.m.report.log_error.call_count)
                self.m.report.log_error.assert_called_with(mock.ANY, message="Timeout, found the order")
                self.assertEqual(123, order.id)

            self.m.reset_mock()
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            with self.subTest(retrieved=False), \
                    mock.patch.object(order, "retrieve_order") as retrieve:
                self.m.ccxt.create_order.side_effect = [
                        portfolio.RequestTimeout,
                        portfolio.RequestTimeout,
                        portfolio.RequestTimeout,
                        portfolio.RequestTimeout,
                        portfolio.RequestTimeout,
                        ]
                retrieve.return_value = False
                order.run()
                self.m.ccxt.create_order.assert_has_calls([
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                    mock.call('ETH/BTC', 'limit', 'buy', D('0.0010'), account='exchange', price=D('0.1')),
                ])
                self.assertEqual(5, self.m.ccxt.create_order.call_count)
                self.assertEqual(5, order.tries)
                self.m.report.log_error.assert_called()
                self.assertEqual(5, self.m.report.log_error.call_count)
                self.m.report.log_error.assert_called_with(mock.ANY, message="Giving up Order(buy long 0.00100000 ETH at 0.1 BTC [pending]) after timeouts", exception=mock.ANY)
                self.assertEqual("error", order.status)

    def test_retrieve_order(self):
        with self.subTest(similar_open_order=True):
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)

            self.m.ccxt.order_precision.return_value = 8
            self.m.ccxt.fetch_orders.return_value = [
                    { # Wrong amount
                        'amount': 0.002, 'cost': 0.1,
                        'datetime': '2018-03-25T15:15:51.000Z',
                        'fee': None, 'filled': 0.0,
                        'id': '1',
                        'info': {
                            'amount': '0.002',
                            'date': '2018-03-25 15:15:51',
                            'margin': 0, 'orderNumber': '1',
                            'price': '0.1', 'rate': '0.1',
                            'side': 'buy', 'startingAmount': '0.002',
                            'status': 'open', 'total': '0.0002',
                            'type': 'limit'
                            },
                        'price': 0.1, 'remaining': 0.002, 'side': 'buy',
                        'status': 'open', 'symbol': 'ETH/BTC',
                        'timestamp': 1521990951000, 'trades': None,
                        'type': 'limit'
                        },
                    { # Margin
                        'amount': 0.001, 'cost': 0.1,
                        'datetime': '2018-03-25T15:15:51.000Z',
                        'fee': None, 'filled': 0.0,
                        'id': '2',
                        'info': {
                            'amount': '0.001',
                            'date': '2018-03-25 15:15:51',
                            'margin': 1, 'orderNumber': '2',
                            'price': '0.1', 'rate': '0.1',
                            'side': 'buy', 'startingAmount': '0.001',
                            'status': 'open', 'total': '0.0001',
                            'type': 'limit'
                            },
                        'price': 0.1, 'remaining': 0.001, 'side': 'buy',
                        'status': 'open', 'symbol': 'ETH/BTC',
                        'timestamp': 1521990951000, 'trades': None,
                        'type': 'limit'
                        },
                    { # selling
                        'amount': 0.001, 'cost': 0.1,
                        'datetime': '2018-03-25T15:15:51.000Z',
                        'fee': None, 'filled': 0.0,
                        'id': '3',
                        'info': {
                            'amount': '0.001',
                            'date': '2018-03-25 15:15:51',
                            'margin': 0, 'orderNumber': '3',
                            'price': '0.1', 'rate': '0.1',
                            'side': 'sell', 'startingAmount': '0.001',
                            'status': 'open', 'total': '0.0001',
                            'type': 'limit'
                            },
                        'price': 0.1, 'remaining': 0.001, 'side': 'sell',
                        'status': 'open', 'symbol': 'ETH/BTC',
                        'timestamp': 1521990951000, 'trades': None,
                        'type': 'limit'
                        },
                    { # Wrong rate
                        'amount': 0.001, 'cost': 0.15,
                        'datetime': '2018-03-25T15:15:51.000Z',
                        'fee': None, 'filled': 0.0,
                        'id': '4',
                        'info': {
                            'amount': '0.001',
                            'date': '2018-03-25 15:15:51',
                            'margin': 0, 'orderNumber': '4',
                            'price': '0.15', 'rate': '0.15',
                            'side': 'buy', 'startingAmount': '0.001',
                            'status': 'open', 'total': '0.0001',
                            'type': 'limit'
                            },
                        'price': 0.15, 'remaining': 0.001, 'side': 'buy',
                        'status': 'open', 'symbol': 'ETH/BTC',
                        'timestamp': 1521990951000, 'trades': None,
                        'type': 'limit'
                        },
                    { # All good
                        'amount': 0.001, 'cost': 0.1,
                        'datetime': '2018-03-25T15:15:51.000Z',
                        'fee': None, 'filled': 0.0,
                        'id': '5',
                        'info': {
                            'amount': '0.001',
                            'date': '2018-03-25 15:15:51',
                            'margin': 0, 'orderNumber': '1',
                            'price': '0.1', 'rate': '0.1',
                            'side': 'buy', 'startingAmount': '0.001',
                            'status': 'open', 'total': '0.0001',
                            'type': 'limit'
                            },
                        'price': 0.1, 'remaining': 0.001, 'side': 'buy',
                        'status': 'open', 'symbol': 'ETH/BTC',
                        'timestamp': 1521990951000, 'trades': None,
                        'type': 'limit'
                        }
                    ]
            result = order.retrieve_order()
            self.assertTrue(result)
            self.assertEqual('5', order.results[0]["id"])
            self.m.ccxt.fetch_my_trades.assert_not_called()
            self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)

        self.m.reset_mock()
        with self.subTest(similar_open_order=False, past_trades=True):
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)

            self.m.ccxt.order_precision.return_value = 8
            self.m.ccxt.fetch_orders.return_value = []
            self.m.ccxt.fetch_my_trades.return_value = [
                    { # Wrong timestamp 1
                        'amount': 0.0006,
                        'cost': 0.00006,
                        'datetime': '2018-03-25T15:15:14.000Z',
                        'id': '1-1',
                        'info': {
                            'amount': '0.0006',
                            'category': 'exchange',
                            'date': '2018-03-25 15:15:14',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '1',
                            'rate': '0.1',
                            'total': '0.00006',
                            'tradeID': '1-1',
                            'type': 'buy'
                            },
                        'order': '1',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983714,
                        'type': 'limit'
                        },
                    { # Wrong timestamp 2
                        'amount': 0.0004,
                        'cost': 0.00004,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '1-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'exchange',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '1',
                            'rate': '0.1',
                            'total': '0.00004',
                            'tradeID': '1-2',
                            'type': 'buy'
                            },
                        'order': '1',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    { # Wrong side 1
                        'amount': 0.0006,
                        'cost': 0.00006,
                        'datetime': '2018-03-25T15:15:54.000Z',
                        'id': '2-1',
                        'info': {
                            'amount': '0.0006',
                            'category': 'exchange',
                            'date': '2018-03-25 15:15:54',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '2',
                            'rate': '0.1',
                            'total': '0.00006',
                            'tradeID': '2-1',
                            'type': 'sell'
                            },
                        'order': '2',
                        'price': 0.1,
                        'side': 'sell',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983754,
                        'type': 'limit'
                        },
                    { # Wrong side 2
                        'amount': 0.0004,
                        'cost': 0.00004,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '2-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'exchange',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '2',
                            'rate': '0.1',
                            'total': '0.00004',
                            'tradeID': '2-2',
                            'type': 'buy'
                            },
                        'order': '2',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    { # Margin trade 1
                        'amount': 0.0006,
                        'cost': 0.00006,
                        'datetime': '2018-03-25T15:15:54.000Z',
                        'id': '3-1',
                        'info': {
                            'amount': '0.0006',
                            'category': 'marginTrade',
                            'date': '2018-03-25 15:15:54',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '3',
                            'rate': '0.1',
                            'total': '0.00006',
                            'tradeID': '3-1',
                            'type': 'buy'
                            },
                        'order': '3',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983754,
                        'type': 'limit'
                        },
                    { # Margin trade 2
                        'amount': 0.0004,
                        'cost': 0.00004,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '3-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'marginTrade',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '3',
                            'rate': '0.1',
                            'total': '0.00004',
                            'tradeID': '3-2',
                            'type': 'buy'
                            },
                        'order': '3',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    { # Wrong amount 1
                        'amount': 0.0005,
                        'cost': 0.00005,
                        'datetime': '2018-03-25T15:15:54.000Z',
                        'id': '4-1',
                        'info': {
                            'amount': '0.0005',
                            'category': 'exchange',
                            'date': '2018-03-25 15:15:54',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '4',
                            'rate': '0.1',
                            'total': '0.00005',
                            'tradeID': '4-1',
                            'type': 'buy'
                            },
                        'order': '4',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983754,
                        'type': 'limit'
                        },
                    { # Wrong amount 2
                        'amount': 0.0004,
                        'cost': 0.00004,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '4-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'exchange',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '4',
                            'rate': '0.1',
                            'total': '0.00004',
                            'tradeID': '4-2',
                            'type': 'buy'
                            },
                        'order': '4',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    { # Wrong price 1
                        'amount': 0.0006,
                        'cost': 0.000066,
                        'datetime': '2018-03-25T15:15:54.000Z',
                        'id': '5-1',
                        'info': {
                            'amount': '0.0006',
                            'category': 'exchange',
                            'date': '2018-03-25 15:15:54',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '5',
                            'rate': '0.11',
                            'total': '0.000066',
                            'tradeID': '5-1',
                            'type': 'buy'
                            },
                        'order': '5',
                        'price': 0.11,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983754,
                        'type': 'limit'
                        },
                    { # Wrong price 2
                        'amount': 0.0004,
                        'cost': 0.00004,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '5-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'exchange',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '5',
                            'rate': '0.1',
                            'total': '0.00004',
                            'tradeID': '5-2',
                            'type': 'buy'
                            },
                        'order': '5',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    { # All good 1
                        'amount': 0.0006,
                        'cost': 0.00006,
                        'datetime': '2018-03-25T15:15:54.000Z',
                        'id': '7-1',
                        'info': {
                            'amount': '0.0006',
                            'category': 'exchange',
                            'date': '2018-03-25 15:15:54',
                            'fee': '0.00150000',
                            'globalTradeID': 1,
                            'orderNumber': '7',
                            'rate': '0.1',
                            'total': '0.00006',
                            'tradeID': '7-1',
                            'type': 'buy'
                            },
                        'order': '7',
                        'price': 0.1,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983754,
                        'type': 'limit'
                        },
                    { # All good 2
                        'amount': 0.0004,
                        'cost': 0.000036,
                        'datetime': '2018-03-25T15:16:54.000Z',
                        'id': '7-2',
                        'info': {
                            'amount': '0.0004',
                            'category': 'exchange',
                            'date': '2018-03-25 15:16:54',
                            'fee': '0.00150000',
                            'globalTradeID': 2,
                            'orderNumber': '7',
                            'rate': '0.09',
                            'total': '0.000036',
                            'tradeID': '7-2',
                            'type': 'buy'
                            },
                        'order': '7',
                        'price': 0.09,
                        'side': 'buy',
                        'symbol': 'ETH/BTC',
                        'timestamp': 1521983814,
                        'type': 'limit'
                        },
                    ]

            result = order.retrieve_order()
            self.assertTrue(result)
            self.assertEqual('7', order.results[0]["id"])
            self.m.ccxt.fetch_orders.assert_called_once_with(symbol="ETH/BTC", since=1521983750)

        self.m.reset_mock()
        with self.subTest(similar_open_order=False, past_trades=False):
            order = portfolio.Order("buy", portfolio.Amount("ETH", "0.001"),
                    D("0.1"), "BTC", "long", self.m, "trade")
            order.start_date = datetime.datetime(2018, 3, 25, 15, 15, 55)

            self.m.ccxt.order_precision.return_value = 8
            self.m.ccxt.fetch_orders.return_value = []
            self.m.ccxt.fetch_my_trades.return_value = []
            result = order.retrieve_order()
            self.assertFalse(result)

@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):
        report_store = market.ReportStore(self.m)
        report_store.add_log({"foo": "bar"})

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

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

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

    def test_merge(self):
        report_store1 = market.ReportStore(self.m, verbose_print=False)
        report_store2 = market.ReportStore(None, verbose_print=False)

        report_store2.log_stage("1")
        report_store1.log_stage("2")
        report_store2.log_stage("3")

        report_store1.merge(report_store2)

        self.assertEqual(3, len(report_store1.logs))
        self.assertEqual(["1", "2", "3"], list(map(lambda x: x["stage"], report_store1.logs)))
        self.assertEqual(6, len(report_store1.print_logs))

    def test_print_log(self):
        report_store = market.ReportStore(self.m)
        with self.subTest(verbose=True),\
                mock.patch.object(store, "datetime") as time_mock,\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            time_mock.now.return_value = datetime.datetime(2018, 2, 25, 2, 20, 10)
            report_store.set_verbose(True)
            report_store.print_log("Coucou")
            report_store.print_log(portfolio.Amount("BTC", 1))
            self.assertEqual(stdout_mock.getvalue(), "2018-02-25 02:20:10: Coucou\n2018-02-25 02:20:10: 1.00000000 BTC\n")

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

    def test_default_json_serial(self):
        report_store = market.ReportStore(self.m)

        self.assertEqual("2018-02-24T00:00:00",
                report_store.default_json_serial(portfolio.datetime(2018, 2, 24)))
        self.assertEqual("1.00000000 BTC",
                report_store.default_json_serial(portfolio.Amount("BTC", 1)))

    def test_to_json(self):
        report_store = market.ReportStore(self.m)
        report_store.logs.append({"foo": "bar"})
        self.assertEqual('[\n  {\n    "foo": "bar"\n  }\n]', report_store.to_json())
        report_store.logs.append({"date": portfolio.datetime(2018, 2, 24)})
        self.assertEqual('[\n  {\n    "foo": "bar"\n  },\n  {\n    "date": "2018-02-24T00:00:00"\n  }\n]', report_store.to_json())
        report_store.logs.append({"amount": portfolio.Amount("BTC", 1)})
        self.assertEqual('[\n  {\n    "foo": "bar"\n  },\n  {\n    "date": "2018-02-24T00:00:00"\n  },\n  {\n    "amount": "1.00000000 BTC"\n  }\n]', report_store.to_json())

    def test_to_json_array(self):
        report_store = market.ReportStore(self.m)
        report_store.logs.append({
            "date": "date1", "type": "type1", "foo": "bar", "bla": "bla"
            })
        report_store.logs.append({
            "date": "date2", "type": "type2", "foo": "bar", "bla": "bla"
            })
        logs = list(report_store.to_json_array())

        self.assertEqual(2, len(logs))
        self.assertEqual(("date1", "type1", '{\n  "foo": "bar",\n  "bla": "bla"\n}'), logs[0])
        self.assertEqual(("date2", "type2", '{\n  "foo": "bar",\n  "bla": "bla"\n}'), logs[1])

    @mock.patch.object(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_stage(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        c = lambda x: x
        report_store.log_stage("foo", bar="baz", c=c, d=portfolio.Amount("BTC", 1))
        print_log.assert_has_calls([
            mock.call("-----------"),
            mock.call("[Stage] foo bar=baz, c=c = lambda x: x, d={'currency': 'BTC', 'value': Decimal('1')}"),
            ])
        add_log.assert_called_once_with({
            'type': 'stage',
            'stage': 'foo',
            'args': {
                'bar': 'baz',
                'c': 'c = lambda x: x',
                'd': {
                    'currency': 'BTC',
                    'value': D('1')
                    }
                }
            })

    @mock.patch.object(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_balances(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        self.m.balances.as_json.return_value = "json"
        self.m.balances.all = { "FOO": "bar", "BAR": "baz" }

        report_store.log_balances(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(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_tickers(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        amounts = {
                "BTC": portfolio.Amount("BTC", 10),
                "ETH": portfolio.Amount("BTC", D("0.3"))
                }
        amounts["ETH"].rate = D("0.1")

        report_store.log_tickers(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')
            })

        add_log.reset_mock()
        compute_value = lambda x: x["bid"]
        report_store.log_tickers(amounts, "BTC", compute_value, "total")
        add_log.assert_called_once_with({
            'type': 'tickers',
            'compute_value': 'compute_value = lambda x: x["bid"]',
            '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(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_dispatch(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        amount = portfolio.Amount("BTC", "10.3")
        amounts = {
                "BTC": portfolio.Amount("BTC", 10),
                "ETH": portfolio.Amount("BTC", D("0.3"))
                }
        report_store.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(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_trades(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        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),
                ]
        report_store.log_trades(matching_and_trades, "only")

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

    @mock.patch.object(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_orders(self, add_log, print_log):
        report_store = market.ReportStore(self.m)

        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]

        report_store.log_orders(orders, tick="tick",
                only="only", compute_value="compute_value")

        print_log.assert_called_once_with("[Orders]")
        self.m.trades.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']
            })

        add_log.reset_mock()
        def compute_value(x, y):
            return x[y]
        report_store.log_orders(orders, tick="tick",
                only="only", compute_value=compute_value)
        add_log.assert_called_with({
            'type': 'orders',
            'only': 'only',
            'compute_value': 'def compute_value(x, y):\n            return x[y]',
            'tick': 'tick',
            'orders': ['order1', 'order2']
            })


    @mock.patch.object(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_order(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        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):
            report_store.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"):
            report_store.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"):
            compute_value = lambda x: (x["bid"] + x["ask"]*2)/3
            report_store.log_order(order_mock, 3,
                    update="adjusting", new_order=new_order_mock,
                    compute_value=compute_value)
            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': 'compute_value = lambda x: (x["bid"] + x["ask"]*2)/3',
                'new_order': 'new_order'
                })

        add_log.reset_mock()
        print_log.reset_mock()
        with self.subTest(update="market_fallback"):
            report_store.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"):
            report_store.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(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_move_balances(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        needed = {
                "BTC": portfolio.Amount("BTC", 10),
                "USDT": 1
                }
        moving = {
                "BTC": portfolio.Amount("BTC", 3),
                "USDT": -2
                }
        report_store.log_move_balances(needed, moving)
        print_log.assert_not_called()
        add_log.assert_called_once_with({
            'type': 'move_balances',
            'debug': False,
            'needed': {
                'BTC': D('10'),
                'USDT': 1
                },
            'moving': {
                'BTC': D('3'),
                'USDT': -2
                }
            })

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

        report_store.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'
            })

        add_log.reset_mock()
        report_store.log_http_request("method", "url", "body",
                "headers", ValueError("Foo"))
        add_log.assert_called_once_with({
            'type': 'http_request',
            'method': 'method',
            'url': 'url',
            'body': 'body',
            'headers': 'headers',
            'status': -1,
            'response': None,
            'error': 'ValueError',
            'error_message': 'Foo',
            })

    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_market(self, add_log):
        report_store = market.ReportStore(self.m)

        report_store.log_market(self.market_args(debug=True, quiet=False), 4, 1)
        add_log.assert_called_once_with({
            "type": "market",
            "commit": "$Format:%H$",
            "args": { "report_path": None, "debug": True, "quiet": False },
            "user_id": 4,
            "market_id": 1,
            })

    @mock.patch.object(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_error(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        with self.subTest(message=None, exception=None):
            report_store.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):
            report_store.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")):
            report_store.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")):
            report_store.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(market.ReportStore, "print_log")
    @mock.patch.object(market.ReportStore, "add_log")
    def test_log_debug_action(self, add_log, print_log):
        report_store = market.ReportStore(self.m)
        report_store.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("unit" in limits, "Unit skipped")
class MainTest(WebMockTestCase):
    def test_make_order(self):
        self.m.get_ticker.return_value = {
                "inverted": False,
                "average": D("0.1"),
                "bid": D("0.09"),
                "ask": D("0.11"),
                }

        with self.subTest(description="nominal case"):
            main.make_order(self.m, 10, "ETH")

            self.m.report.log_stage.assert_has_calls([
                mock.call("make_order_begin"),
                mock.call("make_order_end"),
                ])
            self.m.balances.fetch_balances.assert_has_calls([
                mock.call(tag="make_order_begin"),
                mock.call(tag="make_order_end"),
                ])
            self.m.trades.all.append.assert_called_once()
            trade = self.m.trades.all.append.mock_calls[0][1][0]
            self.assertEqual(False, trade.orders[0].close_if_possible)
            self.assertEqual(0, trade.value_from)
            self.assertEqual("ETH", trade.currency)
            self.assertEqual("BTC", trade.base_currency)
            self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average")
            self.m.trades.run_orders.assert_called_once_with()
            self.m.follow_orders.assert_called_once_with()

            order = trade.orders[0]
            self.assertEqual(D("0.10"), order.rate)

            self.m.reset_mock()
            with self.subTest(compute_value="default"):
                main.make_order(self.m, 10, "ETH", action="dispose",
                        compute_value="ask")

                trade = self.m.trades.all.append.mock_calls[0][1][0]
                order = trade.orders[0]
                self.assertEqual(D("0.11"), order.rate)

        self.m.reset_mock()
        with self.subTest(follow=False):
            result = main.make_order(self.m, 10, "ETH", follow=False)

            self.m.report.log_stage.assert_has_calls([
                mock.call("make_order_begin"),
                mock.call("make_order_end_not_followed"),
                ])
            self.m.balances.fetch_balances.assert_called_once_with(tag="make_order_begin")

            self.m.trades.all.append.assert_called_once()
            trade = self.m.trades.all.append.mock_calls[0][1][0]
            self.assertEqual(0, trade.value_from)
            self.assertEqual("ETH", trade.currency)
            self.assertEqual("BTC", trade.base_currency)
            self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average")
            self.m.trades.run_orders.assert_called_once_with()
            self.m.follow_orders.assert_not_called()
            self.assertEqual(trade.orders[0], result)

        self.m.reset_mock()
        with self.subTest(base_currency="USDT"):
            main.make_order(self.m, 1, "BTC", base_currency="USDT")

            trade = self.m.trades.all.append.mock_calls[0][1][0]
            self.assertEqual("BTC", trade.currency)
            self.assertEqual("USDT", trade.base_currency)

        self.m.reset_mock()
        with self.subTest(close_if_possible=True):
            main.make_order(self.m, 10, "ETH", close_if_possible=True)

            trade = self.m.trades.all.append.mock_calls[0][1][0]
            self.assertEqual(True, trade.orders[0].close_if_possible)

        self.m.reset_mock()
        with self.subTest(action="dispose"):
            main.make_order(self.m, 10, "ETH", action="dispose")

            trade = self.m.trades.all.append.mock_calls[0][1][0]
            self.assertEqual(0, trade.value_to)
            self.assertEqual(1, trade.value_from.value)
            self.assertEqual("ETH", trade.currency)
            self.assertEqual("BTC", trade.base_currency)

            self.m.reset_mock()
            with self.subTest(compute_value="default"):
                main.make_order(self.m, 10, "ETH", action="dispose",
                        compute_value="bid")

                trade = self.m.trades.all.append.mock_calls[0][1][0]
                self.assertEqual(D("0.9"), trade.value_from.value)

    def test_get_user_market(self):
        with mock.patch("main.fetch_markets") as main_fetch_markets,\
                mock.patch("main.parse_config") as main_parse_config:
            with self.subTest(debug=False):
                main_parse_config.return_value = ["pg_config", "report_path"]
                main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                m = main.get_user_market("config_path.ini", 1)

                self.assertIsInstance(m, market.Market)
                self.assertFalse(m.debug)

            with self.subTest(debug=True):
                main_parse_config.return_value = ["pg_config", "report_path"]
                main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                m = main.get_user_market("config_path.ini", 1, debug=True)

                self.assertIsInstance(m, market.Market)
                self.assertTrue(m.debug)

    def test_process(self):
        with mock.patch("market.Market") as market_mock,\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:

            args_mock = mock.Mock()
            args_mock.action = "action"
            args_mock.config = "config"
            args_mock.user = "user"
            args_mock.debug = "debug"
            args_mock.before = "before"
            args_mock.after = "after"
            self.assertEqual("", stdout_mock.getvalue())

            main.process("config", 3, 1, args_mock, "pg_config")

            market_mock.from_config.assert_has_calls([
                mock.call("config", args_mock, pg_config="pg_config", market_id=3, user_id=1),
                mock.call().process("action", before="before", after="after"),
                ])

            with self.subTest(exception=True):
                market_mock.from_config.side_effect = Exception("boo")
                main.process(3, "config", 1, args_mock, "pg_config")
                self.assertEqual("Exception: boo\n", stdout_mock.getvalue())

    def test_main(self):
        with self.subTest(parallel=False):
            with mock.patch("main.parse_args") as parse_args,\
                    mock.patch("main.parse_config") as parse_config,\
                    mock.patch("main.fetch_markets") as fetch_markets,\
                    mock.patch("main.process") as process:

                args_mock = mock.Mock()
                args_mock.parallel = False
                args_mock.user = "user"
                parse_args.return_value = args_mock

                parse_config.return_value = "pg_config"

                fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]

                main.main(["Foo", "Bar"])

                parse_args.assert_called_with(["Foo", "Bar"])
                parse_config.assert_called_with(args_mock)
                fetch_markets.assert_called_with("pg_config", "user")

                self.assertEqual(2, process.call_count)
                process.assert_has_calls([
                    mock.call("config1", 3, 1, args_mock, "pg_config"),
                    mock.call("config2", 1, 2, args_mock, "pg_config"),
                    ])
        with self.subTest(parallel=True):
            with mock.patch("main.parse_args") as parse_args,\
                    mock.patch("main.parse_config") as parse_config,\
                    mock.patch("main.fetch_markets") as fetch_markets,\
                    mock.patch("main.process") as process,\
                    mock.patch("store.Portfolio.start_worker") as start:

                args_mock = mock.Mock()
                args_mock.parallel = True
                args_mock.user = "user"
                parse_args.return_value = args_mock

                parse_config.return_value = "pg_config"

                fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]

                main.main(["Foo", "Bar"])

                parse_args.assert_called_with(["Foo", "Bar"])
                parse_config.assert_called_with(args_mock)
                fetch_markets.assert_called_with("pg_config", "user")

                start.assert_called_once_with()
                self.assertEqual(2, process.call_count)
                process.assert_has_calls([
                    mock.call.__bool__(),
                    mock.call("config1", 3, 1, args_mock, "pg_config"),
                    mock.call.__bool__(),
                    mock.call("config2", 1, 2, args_mock, "pg_config"),
                    ])

    @mock.patch.object(main.sys, "exit")
    @mock.patch("main.os")
    def test_parse_config(self, os, exit):
        with self.subTest(report_path=None):
            args = main.configargparse.Namespace(**{
                "db_host": "host",
                "db_port": "port",
                "db_user": "user",
                "db_password": "password",
                "db_database": "database",
                "report_path": None,
                })

            result = main.parse_config(args)
            self.assertEqual({ "host": "host", "port": "port", "user":
                "user", "password": "password", "database": "database"
                }, result)
            with self.assertRaises(AttributeError):
                args.db_password

        with self.subTest(report_path="present"):
            args = main.configargparse.Namespace(**{
                "db_host": "host",
                "db_port": "port",
                "db_user": "user",
                "db_password": "password",
                "db_database": "database",
                "report_path": "report_path",
                })

            os.path.exists.return_value = False

            result = main.parse_config(args)

            os.path.exists.assert_called_once_with("report_path")
            os.makedirs.assert_called_once_with("report_path")

    def test_parse_args(self):
        with self.subTest(config="config.ini"):
            args = main.parse_args([])
            self.assertEqual("config.ini", args.config)
            self.assertFalse(args.before)
            self.assertFalse(args.after)
            self.assertFalse(args.debug)

            args = main.parse_args(["--before", "--after", "--debug"])
            self.assertTrue(args.before)
            self.assertTrue(args.after)
            self.assertTrue(args.debug)

        with self.subTest(config="inexistant"), \
                self.assertRaises(SystemExit), \
                mock.patch('sys.stderr', new_callable=StringIO) as stdout_mock:
            args = main.parse_args(["--config", "foo.bar"])

    @mock.patch.object(main, "psycopg2")
    def test_fetch_markets(self, psycopg2):
        connect_mock = mock.Mock()
        cursor_mock = mock.MagicMock()
        cursor_mock.__iter__.return_value = ["row_1", "row_2"]

        connect_mock.cursor.return_value = cursor_mock
        psycopg2.connect.return_value = connect_mock

        with self.subTest(user=None):
            rows = list(main.fetch_markets({"foo": "bar"}, None))

            psycopg2.connect.assert_called_once_with(foo="bar")
            cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs")

            self.assertEqual(["row_1", "row_2"], rows)

        psycopg2.connect.reset_mock()
        cursor_mock.execute.reset_mock()
        with self.subTest(user=1):
            rows = list(main.fetch_markets({"foo": "bar"}, 1))

            psycopg2.connect.assert_called_once_with(foo="bar")
            cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs WHERE user_id = %s", 1)

            self.assertEqual(["row_1", "row_2"], rows)


@unittest.skipUnless("unit" in limits, "Unit skipped")
class ProcessorTest(WebMockTestCase):
    def test_values(self):
        processor = market.Processor(self.m)

        self.assertEqual(self.m, processor.market)

    def test_run_action(self):
        processor = market.Processor(self.m)

        with mock.patch.object(processor, "parse_args") as parse_args:
            method_mock = mock.Mock()
            parse_args.return_value = [method_mock, { "foo": "bar" }]

            processor.run_action("foo", "bar", "baz")

            parse_args.assert_called_with("foo", "bar", "baz")

            method_mock.assert_called_with(foo="bar")

            processor.run_action("wait_for_recent", "bar", "baz")

            method_mock.assert_called_with(foo="bar")

    def test_select_step(self):
        processor = market.Processor(self.m)

        scenario = processor.scenarios["sell_all"]

        self.assertEqual(scenario, processor.select_steps(scenario, "all"))
        self.assertEqual(["all_sell"], list(map(lambda x: x["name"], processor.select_steps(scenario, "before"))))
        self.assertEqual(["wait", "all_buy"], list(map(lambda x: x["name"], processor.select_steps(scenario, "after"))))
        self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, 2))))
        self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, "wait"))))

        with self.assertRaises(TypeError):
            processor.select_steps(scenario, ["wait"])

    @mock.patch("market.Processor.process_step")
    def test_process(self, process_step):
        processor = market.Processor(self.m)

        processor.process("sell_all", foo="bar")
        self.assertEqual(3, process_step.call_count)

        steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls))
        scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls))
        kwargs = list(map(lambda x: x[1][2], process_step.mock_calls))
        self.assertEqual(["all_sell", "wait", "all_buy"], steps)
        self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names)
        self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs)

        process_step.reset_mock()

        processor.process("sell_needed", steps=["before", "after"])
        self.assertEqual(3, process_step.call_count)

    def test_method_arguments(self):
        ccxt = mock.Mock(spec=market.ccxt.poloniexE)
        m = market.Market(ccxt, self.market_args())

        processor = market.Processor(m)

        method, arguments = processor.method_arguments("wait_for_recent")
        self.assertEqual(market.Portfolio.wait_for_recent, method)
        self.assertEqual(["delta", "poll"], arguments)

        method, arguments = processor.method_arguments("prepare_trades")
        self.assertEqual(m.prepare_trades, method)
        self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments)

        method, arguments = processor.method_arguments("prepare_orders")
        self.assertEqual(m.trades.prepare_orders, method)

        method, arguments = processor.method_arguments("move_balances")
        self.assertEqual(m.move_balances, method)

        method, arguments = processor.method_arguments("run_orders")
        self.assertEqual(m.trades.run_orders, method)

        method, arguments = processor.method_arguments("follow_orders")
        self.assertEqual(m.follow_orders, method)

        method, arguments = processor.method_arguments("close_trades")
        self.assertEqual(m.trades.close_trades, method)

    def test_process_step(self):
        processor = market.Processor(self.m)

        with mock.patch.object(processor, "run_action") as run_action:
            step = processor.scenarios["sell_needed"][1]

            processor.process_step("foo", step, {"foo":"bar"})

            self.m.report.log_stage.assert_has_calls([
                mock.call("process_foo__1_sell_begin"),
                mock.call("process_foo__1_sell_end"),
                ])
            self.m.balances.fetch_balances.assert_has_calls([
                mock.call(tag="process_foo__1_sell_begin"),
                mock.call(tag="process_foo__1_sell_end"),
                ])

            self.assertEqual(5, run_action.call_count)

            run_action.assert_has_calls([
                mock.call('prepare_trades', {}, {'foo': 'bar'}),
                mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}),
                mock.call('run_orders', {}, {'foo': 'bar'}),
                mock.call('follow_orders', {}, {'foo': 'bar'}),
                mock.call('close_trades', {}, {'foo': 'bar'}),
                ])

        self.m.reset_mock()
        with mock.patch.object(processor, "run_action") as run_action:
            step = processor.scenarios["sell_needed"][0]

            processor.process_step("foo", step, {"foo":"bar"})
            self.m.balances.fetch_balances.assert_not_called()

    def test_parse_args(self):
        processor = market.Processor(self.m)

        with mock.patch.object(processor, "method_arguments") as method_arguments:
            method_mock = mock.Mock()
            method_arguments.return_value = [
                    method_mock,
                    ["foo2", "foo"]
                    ]
            method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"})

            self.assertEqual(method_mock, method)
            self.assertEqual({"foo": "bar2", "foo2": "bar"}, args)

        with mock.patch.object(processor, "method_arguments") as method_arguments:
            method_mock = mock.Mock()
            method_arguments.return_value = [
                    method_mock,
                    ["repartition"]
                    ]
            method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {})

            self.assertEqual(1, len(args["repartition"]))
            self.assertIn("BTC", args["repartition"])

        with mock.patch.object(processor, "method_arguments") as method_arguments:
            method_mock = mock.Mock()
            method_arguments.return_value = [
                    method_mock,
                    ["repartition", "base_currency"]
                    ]
            method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {"base_currency": "USDT"})

            self.assertEqual(1, len(args["repartition"]))
            self.assertIn("USDT", args["repartition"])

        with mock.patch.object(processor, "method_arguments") as method_arguments:
            method_mock = mock.Mock()
            method_arguments.return_value = [
                    method_mock,
                    ["repartition", "base_currency"]
                    ]
            method, args = processor.parse_args("action", {"repartition": { "ETH": 1 }}, {"base_currency": "USDT"})

            self.assertEqual(1, len(args["repartition"]))
            self.assertIn("ETH", args["repartition"])


@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
class AcceptanceTest(WebMockTestCase):
    @unittest.expectedFailure
    def test_success_sell_only_necessary(self):
        # FIXME: catch stdout
        self.m.report.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(market.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(market.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(market.Portfolio, "repartition", return_value=repartition):
            # Action 5
            helper.prepare_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(market.time, "sleep") as sleep:
            # Action 8
            helper.follow_orders(verbose=False)

            sleep.assert_called_with(30)

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