aboutsummaryrefslogblamecommitdiff
path: root/portfolio.py
blob: 45fbef98e72fefac5104223955a1b1897f632131 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                              
           
                                            

                                        




                                           
 





                                                                                             
                                             

                                                

                                                      


                                 
            
                                     
                         
                  
            

                                                         
                           






                                        
                                      






                                        

                                                           
                     
                                                    


                                          
















                                                                     
                                                                                
                                
                             

                                  
                        
 
                                                                                                   

                                           





                                      
                                                                              
                                    
                                                                                         

                                   
                                      
                                   

                                        


                                                                               


                                                                                             
                      
                                                     

                             
                                                                             
                                                                                
                                                              







                                      

                       
                                                                             
                                                                                
                                                              

                             
                                                  
                                                                       
                                                        




                                  
                                                  
                                                                        
                                                        




                                       

                                 

                                                                                  
                                       
 


                                            





                                

                            
                                  

                                                                                  
                                        
 





                                                  













                                                                                                    
 
                                        
                                




                                                                   
                                                                     
 
                                                                  






                                                                  
                                                                            

                
                                                                                        



                                                           
                                                                                     







                                                     
                                    




                                                                                  
                                 
 
                

                                                       

                                                               
                    

                                                               

                                                       
                                                  
                                                                


                      
                                                                                               
                                  
                                                                                            

                                                               
                                                     
                                                                                            
                                                                                         

                
                                                                                                         



                                                                                            
                                                                                                    
 
                
                                                                                                           


                                                                                            
                                                                                                           
                                                                                         
 
                       





























                                                                                                                                               
 
                  

                                         




                                                 
            
                 
               
 
                                                                    





                                                                             
                            
                                                                 



                                                                                                                

                                                     













                                                                  
                                                                       



















                                                                                                      
                             


                                                                                                          
                                 


                                                                   
                

                                                                                                  



                                                             

















                                                                                            

                         

                                                                
                                


                                                                
                
                                   










                                                                                
                             



                                                                                                          
                             



                                                                                                           







                                               
                            
             
                            
 
                                     
                                                         
                        
             
                         
 






                                               






























                                                                                                                 
                                                     

                               
                                                                                 
                                     


                                                                                                    
             
 
                                                            
                                                                    

                        

                                         
                                        

                                                                                  
                                                      
                                      
                                                                                                             
                                                     
                                                  



                                                                                                      

                                                                                          

                                                                        
             
                                    
                 







                                                                                


                                                                              
 

                                                




                                                                          
                                                             

                                                                        

                
                                                                    



                            
                                          



                                                                   
                                    
                                                                 








                                                                        

                




                                                     
                             

                                                      
                                                

                                                          

                                                         

                                        
 




                                                  
                       
                                                  


                                
                            
 

                                  
                                





                                      
 
            
                                                                               
                                            



                                          
                            
                                    

                            
                               
                          
                                                  
                                

                       
                                                        
                            
                                


                                   

                                                         

                 
             






                                     




                                       
                                                                                             
 




                                    
                                                                         
                                                                              
 
                      

                                                                                              

                                                          

                
                                                                                                                                          
                                    

                                     

                                                                                                                     

                                                                                  
 
                         

                              

                                                  


                                          

                          



                                   


                                                                                              








                                                                              
                                      































                                                                                       
 
                     


                                    
                                                   













                                                                         
 
                                              
                                                                                        
                                                 

                                                            
                                


                                                               
                              


                                      
                       
 

                                                                    
                                                 





                                                            


                         








                                                                   



                               

                          
from ccxt import ExchangeError
import time
from decimal import Decimal as D, ROUND_DOWN
# Put your poloniex api key in market.py
from market import market
from json import JSONDecodeError
import requests

# FIXME: correctly handle web call timeouts


class Portfolio:
    URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json"
    liquidities = {}
    data = None

    @classmethod
    def repartition(cls, liquidity="medium"):
        cls.parse_cryptoportfolio()
        liquidities = cls.liquidities[liquidity]
        cls.last_date = sorted(liquidities.keys())[-1]
        return liquidities[cls.last_date]

    @classmethod
    def get_cryptoportfolio(cls):
        try:
            r = requests.get(cls.URL)
        except Exception:
            return
        try:
            cls.data = r.json(parse_int=D, parse_float=D)
        except JSONDecodeError:
            cls.data = None

    @classmethod
    def parse_cryptoportfolio(cls):
        if cls.data is None:
            cls.get_cryptoportfolio()

        def filter_weights(weight_hash):
            if weight_hash[1][0] == 0:
                return False
            if weight_hash[0] == "_row":
                return False
            return True

        def clean_weights(i):
            def clean_weights_(h):
                if h[0].endswith("s"):
                    return [h[0][0:-1], (h[1][i], "short")]
                else:
                    return [h[0], (h[1][i], "long")]
            return clean_weights_

        def parse_weights(portfolio_hash):
            weights_hash = portfolio_hash["weights"]
            weights = {}
            for i in range(len(weights_hash["_row"])):
                weights[weights_hash["_row"][i]] = dict(filter(
                        filter_weights,
                        map(clean_weights(i), weights_hash.items())))
            return weights

        high_liquidity = parse_weights(cls.data["portfolio_1"])
        medium_liquidity = parse_weights(cls.data["portfolio_2"])

        cls.liquidities = {
                "medium": medium_liquidity,
                "high":   high_liquidity,
                }

class Amount:
    def __init__(self, currency, value, linked_to=None, ticker=None, rate=None):
        self.currency = currency
        self.value = D(value)
        self.linked_to = linked_to
        self.ticker = ticker
        self.rate = rate

    def in_currency(self, other_currency, market, rate=None, action=None, compute_value="average"):
        if other_currency == self.currency:
            return self
        if rate is not None:
            return Amount(
                    other_currency,
                    self.value * rate,
                    linked_to=self,
                    rate=rate)
        asset_ticker = Trade.get_ticker(self.currency, other_currency, market)
        if asset_ticker is not None:
            rate = Trade.compute_value(asset_ticker, action, compute_value=compute_value)
            return Amount(
                    other_currency,
                    self.value * rate,
                    linked_to=self,
                    ticker=asset_ticker,
                    rate=rate)
        else:
            raise Exception("This asset is not available in the chosen market")

    def __round__(self, n=8):
        return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN))

    def __abs__(self):
        return Amount(self.currency, abs(self.value))

    def __add__(self, other):
        if other.currency != self.currency and other.value * self.value != 0:
            raise Exception("Summing amounts must be done with same currencies")
        return Amount(self.currency, self.value + other.value)

    def __radd__(self, other):
        if other == 0:
            return self
        else:
            return self.__add__(other)

    def __sub__(self, other):
        if other == 0:
            return self
        if other.currency != self.currency and other.value * self.value != 0:
            raise Exception("Summing amounts must be done with same currencies")
        return Amount(self.currency, self.value - other.value)

    def __mul__(self, value):
        if not isinstance(value, (int, float, D)):
            raise TypeError("Amount may only be multiplied by numbers")
        return Amount(self.currency, self.value * value)

    def __rmul__(self, value):
        return self.__mul__(value)

    def __floordiv__(self, value):
        if not isinstance(value, (int, float, D)):
            raise TypeError("Amount may only be multiplied by integers")
        return Amount(self.currency, self.value / value)

    def __truediv__(self, value):
        return self.__floordiv__(value)

    def __lt__(self, other):
        if other == 0:
            return self.value < 0
        if self.currency != other.currency:
            raise Exception("Comparing amounts must be done with same currencies")
        return self.value < other.value

    def __le__(self, other):
        return self == other or self < other

    def __gt__(self, other):
        return not self <= other

    def __ge__(self, other):
        return not self < other

    def __eq__(self, other):
        if other == 0:
            return self.value == 0
        if self.currency != other.currency:
            raise Exception("Comparing amounts must be done with same currencies")
        return self.value == other.value

    def __ne__(self, other):
        return not self == other

    def __neg__(self):
        return Amount(self.currency, - self.value)

    def __str__(self):
        if self.linked_to is None:
            return "{:.8f} {}".format(self.value, self.currency)
        else:
            return "{:.8f} {} [{}]".format(self.value, self.currency, self.linked_to)

    def __repr__(self):
        if self.linked_to is None:
            return "Amount({:.8f} {})".format(self.value, self.currency)
        else:
            return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to))

class Balance:
    known_balances = {}

    def __init__(self, currency, hash_):
        self.currency = currency
        for key in ["total",
                "exchange_total", "exchange_used", "exchange_free",
                "margin_total", "margin_borrowed", "margin_free"]:
            setattr(self, key, Amount(currency, hash_.get(key, 0)))

        self.margin_position_type = hash_.get("margin_position_type")

        if hash_.get("margin_borrowed_base_currency") is not None:
            base_currency = hash_["margin_borrowed_base_currency"]
            for key in [
                    "margin_liquidation_price",
                    "margin_pending_gain",
                    "margin_lending_fees",
                    "margin_borrowed_base_price"
                    ]:
                setattr(self, key, Amount(base_currency, hash_.get(key, 0)))

    @classmethod
    def in_currency(cls, other_currency, market, compute_value="average", type="total"):
        amounts = {}
        for currency in cls.known_balances:
            balance = cls.known_balances[currency]
            other_currency_amount = getattr(balance, type)\
                    .in_currency(other_currency, market, compute_value=compute_value)
            amounts[currency] = other_currency_amount
        return amounts

    @classmethod
    def currencies(cls):
        return cls.known_balances.keys()

    @classmethod
    def fetch_balances(cls, market):
        all_balances = market.fetch_all_balances()
        for currency, balance in all_balances.items():
            if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
                    currency in cls.known_balances:
                cls.known_balances[currency] = cls(currency, balance)
        return cls.known_balances

    @classmethod
    def dispatch_assets(cls, amount, repartition=None):
        if repartition is None:
            repartition = Portfolio.repartition()
        sum_ratio = sum([v[0] for k, v in repartition.items()])
        amounts = {}
        for currency, (ptt, trade_type) in repartition.items():
            amounts[currency] = ptt * amount / sum_ratio
            if trade_type == "short":
                amounts[currency] = - amounts[currency]
            if currency not in cls.known_balances:
                cls.known_balances[currency] = cls(currency, {})
        return amounts

    @classmethod
    def prepare_trades(cls, market, base_currency="BTC", compute_value="average", debug=False):
        cls.fetch_balances(market)
        values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
        total_base_value = sum(values_in_base.values())
        new_repartition = cls.dispatch_assets(total_base_value)
        # Recompute it in case we have new currencies
        values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
        Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)

    @classmethod
    def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None, debug=False):
        cls.fetch_balances(market)
        values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
        total_base_value = sum(values_in_base.values())
        new_repartition = cls.dispatch_assets(total_base_value)
        Trade.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)

    @classmethod
    def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average", debug=False):
        cls.fetch_balances(market)
        values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
        total_base_value = sum(values_in_base.values())
        new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
        Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)

    def __repr__(self):
        if self.exchange_total > 0:
            if self.exchange_free > 0 and self.exchange_used > 0:
                exchange = " Exch: [✔{} + ❌{} = {}]".format(str(self.exchange_free), str(self.exchange_used), str(self.exchange_total))
            elif self.exchange_free > 0:
                exchange = " Exch: [✔{}]".format(str(self.exchange_free))
            else:
                exchange = " Exch: [❌{}]".format(str(self.exchange_used))
        else:
            exchange = ""

        if self.margin_total > 0:
            if self.margin_free != 0 and self.margin_borrowed != 0:
                margin = " Margin: [✔{} + borrowed {} = {}]".format(str(self.margin_free), str(self.margin_borrowed), str(self.margin_total))
            elif self.margin_free != 0:
                margin = " Margin: [✔{}]".format(str(self.margin_free))
            else:
                margin = " Margin: [borrowed {}]".format(str(self.margin_borrowed))
        elif self.margin_total < 0:
            margin = " Margin: [{} @@ {}/{}]".format(str(self.margin_total),
                    str(self.margin_borrowed_base_price),
                    str(self.margin_lending_fees))
        else:
            margin = ""

        if self.margin_total != 0 and self.exchange_total != 0:
            total = " Total: [{}]".format(str(self.total))
        else:
            total = ""

        return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")"

class Computation:
    computations = {
            "default": lambda x, y: x[y],
            "average": lambda x, y: x["average"],
            "bid": lambda x, y: x["bid"],
            "ask": lambda x, y: x["ask"],
            }

class Trade:
    debug = False
    trades = []

    def __init__(self, value_from, value_to, currency, market=None):
        # We have value_from of currency, and want to finish with value_to of
        # that currency. value_* may not be in currency's terms
        self.currency = currency
        self.value_from = value_from
        self.value_to = value_to
        self.orders = []
        self.market = market
        assert self.value_from.currency == self.value_to.currency
        if self.value_from != 0:
            assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency
        elif self.value_from.linked_to is None:
            self.value_from.linked_to = Amount(self.currency, 0)
        self.base_currency = self.value_from.currency

    fees_cache = {}
    @classmethod
    def fetch_fees(cls, market):
        if market.__class__ not in cls.fees_cache:
            cls.fees_cache[market.__class__] = market.fetch_fees()
        return cls.fees_cache[market.__class__]

    ticker_cache = {}
    ticker_cache_timestamp = time.time()
    @classmethod
    def get_ticker(cls, c1, c2, market, refresh=False):
        def invert(ticker):
            return {
                    "inverted": True,
                    "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2,
                    "original": ticker,
                    }
        def augment_ticker(ticker):
            ticker.update({
                "inverted": False,
                "average": (ticker["bid"] + ticker["ask"] ) / 2,
                })

        if time.time() - cls.ticker_cache_timestamp > 5:
            cls.ticker_cache = {}
            cls.ticker_cache_timestamp = time.time()
        elif not refresh:
            if (c1, c2, market.__class__) in cls.ticker_cache:
                return cls.ticker_cache[(c1, c2, market.__class__)]
            if (c2, c1, market.__class__) in cls.ticker_cache:
                return invert(cls.ticker_cache[(c2, c1, market.__class__)])

        try:
            cls.ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2))
            augment_ticker(cls.ticker_cache[(c1, c2, market.__class__)])
        except ExchangeError:
            try:
                cls.ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1))
                augment_ticker(cls.ticker_cache[(c2, c1, market.__class__)])
            except ExchangeError:
                cls.ticker_cache[(c1, c2, market.__class__)] = None
        return cls.get_ticker(c1, c2, market)

    @classmethod
    def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
        cls.debug = cls.debug or debug
        base_currency = sum(values_in_base.values()).currency
        for currency in Balance.currencies():
            if currency == base_currency:
                continue
            value_from = values_in_base.get(currency, Amount(base_currency, 0))
            value_to = new_repartition.get(currency, Amount(base_currency, 0))
            if value_from.value * value_to.value < 0:
                trade_1 = cls(value_from, Amount(base_currency, 0), currency, market=market)
                if only is None or trade_1.action == only:
                    cls.trades.append(trade_1)
                trade_2 = cls(Amount(base_currency, 0), value_to, currency, market=market)
                if only is None or trade_2.action == only:
                    cls.trades.append(trade_2)
            else:
                trade = cls(
                    value_from,
                    value_to,
                    currency,
                    market=market
                    )
                if only is None or trade.action == only:
                    cls.trades.append(trade)
        return cls.trades

    @classmethod
    def prepare_orders(cls, only=None, compute_value="default"):
        for trade in cls.trades:
            if only is None or trade.action == only:
                trade.prepare_order(compute_value=compute_value)

    @classmethod
    def move_balances(cls, market):
        needed_in_margin = {} 
        for trade in cls.trades:
            if trade.trade_type == "short":
                if trade.value_to.currency not in needed_in_margin:
                    needed_in_margin[trade.value_to.currency] = 0
                needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
        for currency, needed in needed_in_margin.items():
            current_balance = Balance.known_balances[currency].margin_free
            delta = (needed - current_balance).value
            # FIXME: don't remove too much if there are open margin position
            if delta > 0:
                if cls.debug:
                    print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta))
                else:
                    market.transfer_balance(currency, delta, "exchange", "margin")
            elif delta < 0:
                if cls.debug:
                    print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta))
                else:
                    market.transfer_balance(currency, -delta, "margin", "exchange")

    @property
    def action(self):
        if self.value_from == self.value_to:
            return None
        if self.base_currency == self.currency:
            return None

        if self.value_from < self.value_to:
            return "acquire"
        else:
            return "dispose"

    def order_action(self, inverted):
        if (self.value_from < self.value_to) != inverted:
            return "buy"
        else:
            return "sell"

    @property
    def trade_type(self):
        if self.value_from + self.value_to < 0:
            return "short"
        else:
            return "long"

    @property
    def filled_amount(self):
        filled_amount = 0
        for order in self.orders:
            filled_amount += order.filled_amount
        return filled_amount

    def update_order(self, order, tick):
        new_order = None
        if tick in [0, 1, 3, 4, 6]:
            print("{}, tick {}, waiting".format(order, tick))
        elif tick == 2:
            self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2)
            new_order = self.orders[-1]
            print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
        elif tick ==5:
            self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3)
            new_order = self.orders[-1]
            print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
        elif tick >= 7:
            if tick == 7:
                print("{}, tick {}, fallbacking to market value".format(order, tick))
            if (tick - 7) % 3 == 0:
                self.prepare_order(compute_value="default")
                new_order = self.orders[-1]
                print("{}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))

        if new_order is not None:
            order.cancel()
            new_order.run()

    def prepare_order(self, compute_value="default"):
        if self.action is None:
            return
        ticker = Trade.get_ticker(self.currency, self.base_currency, self.market)
        inverted = ticker["inverted"]
        if inverted:
            ticker = ticker["original"]
        rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
        # 0.1

        delta_in_base = abs(self.value_from - self.value_to)
        # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)

        if not inverted:
            currency = self.base_currency
            # BTC
            if self.action == "dispose":
                # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
                # At rate 1 Foo = 0.1 BTC
                value_from = self.value_from.linked_to
                # value_from = 100 FOO
                value_to = self.value_to.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
                # value_to   = 10 FOO (1 BTC * 1/0.1)
                delta = abs(value_to - value_from)
                # delta      = 90 FOO
                # Action: "sell" "90 FOO" at rate "0.1" "BTC" on "market"

                # Note: no rounding error possible: if we have value_to == 0, then delta == value_from
            else:
                delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate)
                # I want to buy 9 / 0.1 FOO
                # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market"
        else:
            currency = self.currency
            # FOO
            delta = delta_in_base
            # sell: 
            #   I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
            #   At rate 1 Foo = 0.1 BTC
            #   Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market
            # buy:
            #   I want to buy 9 / 0.1 FOO
            #   Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market"
            if self.value_to == 0:
                rate = self.value_from.linked_to.value / self.value_from.value
                # Recompute the rate to avoid any rounding error

        close_if_possible = (self.value_to == 0)

        if delta <= self.filled_amount:
            print("Less to do than already filled: {} <= {}".format(delta,
                self.filled_amount))
            return

        self.orders.append(Order(self.order_action(inverted),
            delta - self.filled_amount, rate, currency, self.trade_type,
            self.market, self, close_if_possible=close_if_possible))

    @classmethod
    def compute_value(cls, ticker, action, compute_value="default"):
        if action == "buy":
            action = "ask"
        if action == "sell":
            action = "bid"
        if isinstance(compute_value, str):
            compute_value = Computation.computations[compute_value]
        return compute_value(ticker, action)

    @classmethod
    def all_orders(cls, state=None):
        all_orders = sum(map(lambda v: v.orders, cls.trades), [])
        if state is None:
            return all_orders
        else:
            return list(filter(lambda o: o.status == state, all_orders))

    @classmethod
    def run_orders(cls):
        for order in cls.all_orders(state="pending"):
            order.run()

    @classmethod
    def follow_orders(cls, verbose=True, sleep=None):
        if sleep is None:
            sleep = 7 if cls.debug else 30
        tick = 0
        while len(cls.all_orders(state="open")) > 0:
            time.sleep(sleep)
            tick += 1
            for order in cls.all_orders(state="open"):
                if order.get_status() != "open":
                    if verbose:
                        print("finished {}".format(order))
                else:
                    order.trade.update_order(order, tick)
        if verbose:
            print("All orders finished")

    @classmethod
    def update_all_orders_status(cls):
        for order in cls.all_orders(state="open"):
            order.get_status()

    def __repr__(self):
        return "Trade({} -> {} in {}, {})".format(
                self.value_from,
                self.value_to,
                self.currency,
                self.action)

    @classmethod
    def print_all_with_order(cls):
        for trade in cls.trades:
            trade.print_with_order()

    def print_with_order(self):
        print(self)
        for order in self.orders:
            print("\t", order, sep="")

class Order:
    def __init__(self, action, amount, rate, base_currency, trade_type, market,
            trade, close_if_possible=False):
        self.action = action
        self.amount = amount
        self.rate = rate
        self.base_currency = base_currency
        self.market = market
        self.trade_type = trade_type
        self.results = []
        self.mouvements = []
        self.status = "pending"
        self.trade = trade
        self.close_if_possible = close_if_possible
        self.debug = trade.debug

    def __repr__(self):
        return "Order({} {} {} at {} {} [{}]{})".format(
                self.action,
                self.trade_type,
                self.amount,
                self.rate,
                self.base_currency,
                self.status,
                " ✂" if self.close_if_possible else "",
                )

    @property
    def account(self):
        if self.trade_type == "long":
            return "exchange"
        else:
            return "margin"

    @property
    def pending(self):
        return self.status == "pending"

    @property
    def finished(self):
        return self.status == "closed" or self.status == "canceled" or self.status == "error"

    @property
    def id(self):
        return self.results[0]["id"]

    def run(self):
        symbol = "{}/{}".format(self.amount.currency, self.base_currency)
        amount = round(self.amount, self.market.order_precision(symbol)).value

        if self.debug:
            print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
                symbol, self.action, amount, self.rate, self.account))
            self.status = "open"
            self.results.append({"debug": True, "id": -1})
        else:
            try:
                self.results.append(self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account))
                self.status = "open"
            except Exception as e:
                self.status = "error"
                print("error when running market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
                    symbol, self.action, amount, self.rate, self.account))
                self.error_message = str("{}: {}".format(e.__class__.__name__, e))
                print(self.error_message)

    def get_status(self):
        if self.debug:
            return self.status
        # other states are "closed" and "canceled"
        if self.status == "open":
            self.fetch()
            if self.status != "open":
                self.mark_finished_order()
        return self.status

    def mark_finished_order(self):
        if self.debug:
            return
        if self.status == "closed":
            if self.trade_type == "short" and self.action == "buy" and self.close_if_possible:
                self.market.close_margin_position(self.amount.currency, self.base_currency)

    fetch_cache_timestamp = None
    def fetch(self, force=False):
        if self.debug or (not force and self.fetch_cache_timestamp is not None
                and time.time() - self.fetch_cache_timestamp < 10):
            return
        self.fetch_cache_timestamp = time.time()

        self.results.append(self.market.fetch_order(self.id))
        result = self.results[-1]
        self.status = result["status"]
        # Time at which the order started
        self.timestamp = result["datetime"]
        self.fetch_mouvements()

        # FIXME: consider open order with dust remaining as closed

    @property
    def dust_amount_remaining(self):
        return self.remaining_amount < 0.001

    @property
    def remaining_amount(self):
        if self.status == "open":
            self.fetch()
        return self.amount - self.filled_amount

    @property
    def filled_amount(self):
        if self.status == "open":
            self.fetch()
        filled_amount = Amount(self.amount.currency, 0)
        for mouvement in self.mouvements:
            filled_amount += mouvement.total
        return filled_amount

    def fetch_mouvements(self):
        mouvements = self.market.privatePostReturnOrderTrades({"orderNumber": self.id})
        self.mouvements = []

        for mouvement_hash in mouvements:
            self.mouvements.append(Mouvement(self.amount.currency,
                self.base_currency, mouvement_hash))

    def cancel(self):
        if self.debug:
            self.status = "canceled"
            return
        self.market.cancel_order(self.result['id'])
        self.fetch()

class Mouvement:
    def __init__(self, currency, base_currency, hash_):
        self.currency = currency
        self.base_currency = base_currency
        self.id = hash_["id"]
        self.action = hash_["type"]
        self.fee_rate = D(hash_["fee"])
        self.date = datetime.strptime(hash_["date"], '%Y-%m-%d %H:%M:%S')
        self.rate = D(hash_["rate"])
        self.total = Amount(currency, hash_["amount"])
        # rate * total = total_in_base
        self.total_in_base = Amount(base_currency, hash_["total"])

def print_orders(market, base_currency="BTC"):
    Balance.prepare_trades(market, base_currency=base_currency, compute_value="average")
    Trade.prepare_orders(compute_value="average")
    for currency, balance in Balance.known_balances.items():
        print(balance)
    Trade.print_all_with_order()

def make_orders(market, base_currency="BTC"):
    Balance.prepare_trades(market, base_currency=base_currency)
    for trade in Trade.trades:
        print(trade)
        for order in trade.orders:
            print("\t", order, sep="")
            order.run()

def process_sell_all_sell(market, base_currency="BTC", debug=False):
    Balance.prepare_trades_to_sell_all(market, debug=debug)
    Trade.prepare_orders(compute_value="average")
    print("------------------")
    for currency, balance in Balance.known_balances.items():
        print(balance)
    print("------------------")
    Trade.print_all_with_order()
    print("------------------")
    Trade.run_orders()
    Trade.follow_orders()

def process_sell_all_buy(market, base_currency="BTC", debug=False):
    Balance.prepare_trades(market, debug=debug)
    Trade.prepare_orders()
    print("------------------")
    for currency, balance in Balance.known_balances.items():
        print(balance)
    print("------------------")
    Trade.print_all_with_order()
    print("------------------")
    Trade.move_balances(market)
    Trade.run_orders()
    Trade.follow_orders()

if __name__ == '__main__':
    print_orders(market)