aboutsummaryrefslogblamecommitdiff
path: root/test.py
blob: c229801246219870d99e5f8c574793dd96f1ddcc (plain) (tree)
1
2
3
4
5
6
7
8
9
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
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
          

                
               
                                
                         

                    
                       
                
                                     
 









                                                     


                                         


                                                                            




                                            



                                                             
                         
                                                     







                                                    
                                                          
                                                                     



                                     





                                               
                                                      











                                             











                                                                                         









                                                                   








                                                                                                                    
                                                                                              






                                                                                                                     
                                                                                              






                                                                                                                      
                                                                                              






                                                                                                                      
                                                                                              

















                                                                                            

























































                                                                                      






















































































































































































































































































































                                                                                                                                                        
                                                      




























































































                                                                                     
                                     


                                          
                                                                 

                                               
                                                                  
 

                                                                 

























































                                                                                            
 
                                         




























































                                                                                               


                                                               
                                                        







                                                      

                                                                  
 



                                                                                               
 


                                                               
                           




                                         
                                
                                                                              
                                      
                                                   
                                              
 
                                          

                                             

                                                                                                           

                          
                                        
                                                          
                           
                                                  
                                 
                                                           

                          
                                        
                                                          
                           
                                                 

                                             
                                                            
 































                                                                                         
                                                      
                                  
                          

                                                 

                                                


                                            
                                                                   
 

                                                         
 
                                                                           
 

                                               

                                    
                                        

                                 
                                                                
 
                                                              



                                                                   
                                                                                                       

                                                            
                                                                                     

                                                            
                                                                            

                                                          




                                                                      









                                                    

                                                 



                                                             
                                                





                                                    

                                              
                         
                                                





                                            

                                                 
 
                                                             

                                                             
                                                





                                                    




                                               
                                                                   
 


                                            

                                                              






                                            

                                                              



                                            

                                                             
 


                                          
                            

                                            

                                                             












                                               



































                                               
















                                               





















                                                                



















                                                                                                                           










                                                                                      
                                                      
                                   
                          




                                            


                                      














                                                                           

                                                                          

                                                                 
                                                                  
 

                                                 


                                                                               





                                                                                                                      
 


                                                                                      
                                                               

                                                                                                                        
 
                                                                                        

                                                                                   




                                                                                                                  
 
                                                               

                                                                                                                                     
 








                                                                                       
                                                           

                                                          
                                                      






                                                         
                                                        










                                                              
                                                                  

                                
                                                                   

                                 







                                                                           



                                                   
 
                                                                                                 


                                               
                                                                                                       

                                       



                                               
                 
 
                                                        


                                                    
 







                                                                  
                                                            











                                                           
                                                               













                                                       
                                                            












                                                                
                                                               


                                               
 
                              
                                                        





                                                 
 
                                                       


                                                                           
                                    

                                           
                 
                                





                                                   
                                                                    

                                            
                                              
                                                            













                                                         
 
                                                
 







                                                                  

                                                                        
                                                                    
                                                                    
 
 
                                            
                                                       
                                                        



                                                          
                                                     
                                                                           
 























                                                                               
                                            












                                                                              
                                                  






                                                          


                                                                   




                                                                      
                                                           



                                                                 
                                                          


                                 
                                                                                                    





                                                        
                                                             


                                                
                                                     
                                                                           
 


                                                                      
                                                                        



                                                                     
                                                                        



                                                                     
                                                                        
 
                                                       


                                                                                                               
                                                                                     
 
                                 
 

                                                               
 
                         

                                                                             
                     
                                                                                              

                                                                                                 
 
















                                                                                                          
                                                 




                                                              
                                                              

                                                             
                                                              
                                                                                                       
                                                                          









































































































                                                                                                                                          
                                     
                                    
                                                                                          




                                                                   
                                                                                    

                                                        
                                                               

                                                                                


                                                                               

                                                      
                                                                                        

                                                        
                                                          


                                                                               





























































                                                                                                                                                                                        

                            
                                                                   


































                                                                                        

                                
                                                        












                                                                                   
                                                        























                                                                                   
                                                        
















































































                                                                                                 
                                                      
                                      

                                                                                      












                                                          






                           
 


                                                                                            
 






                                                                            
 
                                     




































                                                             

                                 
                                 
 

                                                  

                                                  


                                   
 

                                           
                                           
 
                                    

                                                                             
                                                     
                                                                                 
 
                                             
 
                                                       

                                                                         
                                                                             


                                              
                                             


                                  
                                              

                                                                             
                                                                               




                                        

                                                                 
 
                                          




                                                    











                                                                              




                                       

                                                                              

                         














                                                       


                                               
 
                                         

                                        
                                                          


                                                                 











                                                                              
 


                                                  
 
















                                                   

                                 
                                  
                                 
                                  
                                 
                                   







                                                                         
 
                                                      































                                               








                                                   










                                                 
 
                                                  


                                                         
                                                                         
                                   
                                              
 
                                                                       

                                                         
                                                                         
                               
                                              
 
                                                                                             

                                                         
                                                                         
                                       
                                              
 

                                                                        
 
                                                   
 




                                                                                      

                                                        



                                                                                             
 
                                                       




                                                                        
 
                                                           
 
                            

                                           
                                             
                 
                                                   
 
                                                                                
                                                          
                                                        

                                                        
                                                          

                                                                                  
                                                         

                              


                                                   










                                                 
                                                                              
 






                                              

                                                   



                                     
                                         



                                           
                                                      























                                                                                 
                                                      
                                 
 
                                    


                                                              
                                                                    

                                                    
                                              

                                               
                                                                 
                                               
                                                                

                                               
                                                                



                                                                 
 
                                               
                                                                    
                                                       
 
                          


                                                              
                                                                    
 



                                                             
                                                 
                                                                    





                                                              
                                                                    





                                                           
                                                                    
 
                                                 
 
                                


                                                              
                                                                    
 



                                                      



                                                           
                                                                    
 



                                                      




                                                              
                                                                    





                                                           
                                                                    


                                                   
                                




                                                                        
 

                                                                              
 















                                                                               
 





                                                                         
 






                                                                               
 
                                                
 


                                                                               
 
                                               
 


                                                                           
 
 



                                                              
                                                                    

                            
                                                                          

                            
                                                                           


                                   


                                                                                
 







                                                                                                      


                                                              
                                                                      






                                                                 
                                                                        







                                                  

                                                              






                                                                       
                                                                        



                                                                    
                                                                                                             
                                                  
                                                                                         









                                                                      
                                                                        



                                                                    
                                                                                                             

                                                                         
                                                      

                                                   


















                                                                                                             







                                                                     
                                                                        



                                                                   
                                                                                                            


                                                                        
                                                  









                                                                     
                                                                        



                                                                    
                                                                                                             

                                                                          
                                                      

                                                  
                                                                             







                                                                       
                                                                        



                                                                   
                                                                                                                    

                                                                              
                                                    









                                                                     
                                                                        



                                                                    
                                                                                                                     

                                                                             
                                                    










                                                              
                                                                    
                                                   

                                 
                                      


                                                      
                                                                              
                                                                             



                                       









                                                               
                                           



                                                                   



                                   









                                                               
                                           



                                                                   



                                   














                                                                   



                                   
                                            

                              
                                      



                                                                         

                                                                       





                                                                           
                                                               



                                       
                                                

                                
                                      


                                                      
                                                                                                
                                                           



                                       
                                                
 
 
                                    


                                                              
                                                                    






                                                    









                                                             


                                        

                                                                 
 























                                                                                                                                  
 




                                                                    

                                   



                                            
                                               

















                                                                          
                         


                                                              
                                                                    

                                                                                                                 
 



                                                              
                                                                    







                                                         


























                                                                                                 




















                                                                   


































                                                                    
                                 




                                                                       
 




                                                               
 






































                                                                          


                                                                   
                                                         









                                                                                                 
                                                         

                                                           


                                                           



                                                                   
                                                         
                                                                   
                                                          



                                                                   
                                                          










                                                                                                    
                                                                 
                 
                                                                  



                                                                 
                                                                  




                                                                   
                                                         




                                                
                                                                                        



                                                    
                                                                                      
                                                                   
                                                         


                                                  
                                       
                                                                   
                                                          

                                       
                            

                                   

                                                                          


                                   
                                                             

                                                                   
                                                          


                                        
                                                             

                                                                    
                                                          


                                       
                                                             

                                                                   
                                                         


                                       
                                                             
 
                           

                                                                   
                                                          



                                       

                                                             

                                                           

                                                                








                                                                   
                                                   
                                                
 






                                                    
 
                                                               




                                                              
                                                    
 
                                            



                                                       
                             
                                                                
                                                        

                                                
                                     
                                      
                               
                                                                       
                                                             

                                                           
                                                               

                                                       
                                
                                                                       
                                                             





                                                        

                                      

                                                      
                                
                                                                       
                                                             





                                                          


                                      


                                                    
                                                                       
                                                             
                       

                                                                                                                                                          



                                                   
                                             
                                       
                                
                                                                       

                                                                 
                       
                                                         


                                                   

                                             
                                                                       

                                                                    
                       
                                                         

                                                   
                                                        
 
                                             


                                                                                                 
                                                             
                                                                         
                       
                                                         



                                                    
                                                    
                                             
                                                    
                                                                                                 
                                                                            
                                                             





                                                    
                       



































                                                                                                          
                                                   




                                                                                                                                                             
 
                           









































                                                                                                                                                                                     



















































































































































































































































































































































































































































































































                                                                                                                                                                                
 



                                                       
                                                          












                                                                                     








                                                                             






                                                                                                                               
 






                                                                                                      


















                                                                                      

                                                 
 
                                                                                

                               
                                                 
                                        

                                                       

                                         

                                                        
 











                                                                                              
                                                          
 
                             
                                                 
                                         
                                                                   
                                                                               
                                                                                  


                                                              
                                                                                                                          


                                                                               


                                                              

                                                        







                                                                                  
                           

                                                 
                                                                                    
                                                                           
                                                                                                                                  
                                                                        
                                                                                                                                                                             
 













                                                                                              

                                                       
                                                 
                                                 

                                                                                   

                                     
                                                                                                              
              











                                         
 





                                                            
 
                                            




                                    




                                         
 

                                                       
                                                   
                                                 





                                                        
                                                                    
















                                         


















                                                                        

                                                       
                                                    
                                                 




                                                        
                                                                           














                                               

                                                       
                                                  
                                                 








                                                           
                                                            




                                     
                           





                                                 




                                                       







                                                   
                                                    


                                                           
                                                                            








                                             













                                                                                 

                                                       
                                                 
                                                 









                                                               
                                                                













                                                                            
                                                                   












                                                                                    
                                                               
                                                 
                                                                 
                                                





                                                                                                                       
                                                                                       





                                                    
                                                 













                                                                                                        
                                                  










                                                                                                                                      

                                                       
                                                         
                                                 







                                                   
                                                      


                                         
                           









                               

                                                       
                                                        
                                                 



                                  
                                                              











                                         

                                                       
                                                 
                                                 
                                                        
                                            











                                                               
                                                           














                                                                     
                                                                         














                                                                      
                                                                                        












                                                   

                                                       
                                                        

                                                 






                                                        
                                                      
                                








                                                      
                                              























                                                                                                
                                                                    







                                                                    
                                                                     


















                                                                                                
                                                                   






                                                                
                                                                      





                                                                     
                                                                








                                                                
                                                                    




                                                                    


                                                                     

                                                                             
                                                                                    
                                                              





                                                                             
                                                                                    
                                                                          



                                                       

                                                         
                                                                               
 






                                       

                                                        
                                                                               

                                                      
                                                                                                                         




                                                                              
                                                                                   


                                                                            




                                                                       
 




                                                   
 
                                                                        
 
                                                                                   
 
                                         
 


                                                                     
 

                                                       

                                                                                      















                                                                        
                                                                                   










                                                                     
                                                                                      
                                         
                                                                                      
                      
 



                                                        








                                                                
                                                    
















                                                                 
                                                    









                                                                               
                                                    




                                                                                                        







                                                       
 



                                                                      
 




















                                                                                       
                                                                                                       








                                                               
                                                                                                                             

                                                      
 
 


                                                      
                                            
 
                                                  
 
                              
                                            
 


                                                                      
 
                                                     
 
                                                              
 
                                                     
 
                                                                 
 
                                                     

                               
                                            











                                                                                                                        
                                                
                                         
                                            

















                                                                               
                                                   
 
                                       

                                                                         
                                                                  
                                                      




















                                                                                                           
                                            
































                                                                                                             
                                            











































                                                                                                                             
 
 
                                                                  

                                      
                                               
                             
                                           

                         


                                               


                                      


                                               


                                      


                                                  



                                         





                                             


























                                            





                                            
                                    
                                          








                                                                          
                                                              
                                                      
                                                                                          
                      
                                         
 
                                             




                                                                              
                                         


                                                                                  
 


                                                                                  
 


                                                                                  
 

                                                                                  
                                                     
 


                                                                                  
 


                                                                                  

                  
                                                                                                             
 
                                                                     






                                                             
                                                                                       

                                           
                                                
                                                         

                                                     
                                                








                                                         
                                               

                  
                                         



                                                      


                                                                                                   
                                                                  



                                                                 
                                                              
                      
                                               







                                                    



                                                   


                                          



                                                 


                                        



                                               


                                      



                                               


                                      
                                                              
 
                                                                                          
                      
                                                                                  
 
                                             





                                                                                    



                                                                                  
 


                                                                                  


                                       


                                                                                  
 


                                                                                  
 


                                                                                  
 


                                                                                  

                  
                                                                                                
 
                                                                     

                                                                                                

                                                        
                                                          
 
                                                                                               
                                                         












                                                                                    
 



                                 

                  
                                           
 
                                                              
                      
                                               


                                        

                          
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):
        return type('Args', (object,), { "debug": debug, "quiet": quiet })()

    def setUp(self):
        super(WebMockTestCase, self).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(WebMockTestCase, self).tearDown()

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

        self.s = market.ccxt.poloniexE()

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

    def test__init(self):
        with 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')

    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(PortfolioTest, self).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(MarketTest, self).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)
            with self.subTest(quiet=True):
                m = market.Market(self.ccxt, self.market_args(quiet=True))
                report_store.assert_called_with(m, verbose_print=False)

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

    @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(), 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_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(), 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(),
                pg_config="pg_config", report_path="present", 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(BalanceStoreTest, self).setUp()

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

    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)


    @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 12:00:12", "rate": "0.1",
                    "amount": "3", "total": "0.3"
                    },
                {
                    "tradeID": 43, "type": "buy", "fee": "0.0015",
                    "date": "2017-12-30 13:00:12", "rate": "0.2",
                    "amount": "2", "total": "0.4"
                    }
            ]
        order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                D("0.1"), "BTC", "long", 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(42, order.mouvements[0].id)
        self.assertEqual(43, 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_finished_order")
    def test_fetch(self, mark_finished_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()
            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_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()

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

    @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, "report_path", "pg_config")

            market_mock.from_config.assert_has_calls([
                mock.call("config", args_mock, pg_config="pg_config", market_id=3, user_id=1, report_path="report_path"),
                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, "report_path", 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.config = "config"
                args_mock.user = "user"
                parse_args.return_value = args_mock

                parse_config.return_value = ["pg_config", "report_path"]

                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("config")
                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, "report_path", "pg_config"),
                    mock.call("config2", 1, 2, args_mock, "report_path", "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.config = "config"
                args_mock.user = "user"
                parse_args.return_value = args_mock

                parse_config.return_value = ["pg_config", "report_path"]

                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("config")
                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, "report_path", "pg_config"),
                    mock.call.__bool__(),
                    mock.call("config2", 1, 2, args_mock, "report_path", "pg_config"),
                    ])

    @mock.patch.object(main.sys, "exit")
    @mock.patch("main.configparser")
    @mock.patch("main.os")
    def test_parse_config(self, os, configparser, exit):
        with self.subTest(pg_config=True, report_path=None):
            config_mock = mock.MagicMock()
            configparser.ConfigParser.return_value = config_mock
            def config(element):
                return element == "postgresql"

            config_mock.__contains__.side_effect = config
            config_mock.__getitem__.return_value = "pg_config"

            result = main.parse_config("configfile")

            config_mock.read.assert_called_with("configfile")

            self.assertEqual(["pg_config", None], result)

        with self.subTest(pg_config=True, report_path="present"):
            config_mock = mock.MagicMock()
            configparser.ConfigParser.return_value = config_mock

            config_mock.__contains__.return_value = True
            config_mock.__getitem__.side_effect = [
                    {"report_path": "report_path"},
                    {"report_path": "report_path"},
                    "pg_config",
                    ]

            os.path.exists.return_value = False
            result = main.parse_config("configfile")

            config_mock.read.assert_called_with("configfile")
            self.assertEqual(["pg_config", "report_path"], result)
            os.path.exists.assert_called_once_with("report_path")
            os.makedirs.assert_called_once_with("report_path")

        with self.subTest(pg_config=False),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            config_mock = mock.MagicMock()
            configparser.ConfigParser.return_value = config_mock
            result = main.parse_config("configfile")

            config_mock.read.assert_called_with("configfile")
            exit.assert_called_once_with(1)
            self.assertEqual("no configuration for postgresql in config file\n", stdout_mock.getvalue())

    @mock.patch.object(main.sys, "exit")
    def test_parse_args(self, exit):
        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)

            exit.assert_not_called()

        with self.subTest(config="inexistant"),\
                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
            args = main.parse_args(["--config", "foo.bar"])
            exit.assert_called_once_with(1)
            self.assertEqual("no config file found, exiting\n", stdout_mock.getvalue())

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