import ccxt import time # Put your poloniex api key in market.py from market import market # FIXME: Améliorer le bid/ask # FIXME: J'essayais d'utiliser plus de bitcoins que j'en avais à disposition # FIXME: better compute moves to avoid rounding errors def static_var(varname, value): def decorate(func): setattr(func, varname, value) return func return decorate class Portfolio: URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" liquidities = {} data = None @classmethod def repartition_pertenthousand(cls, liquidity="medium"): cls.parse_cryptoportfolio() liquidities = cls.liquidities[liquidity] last_date = sorted(liquidities.keys())[-1] return liquidities[last_date] @classmethod def get_cryptoportfolio(cls): import json import urllib3 urllib3.disable_warnings() http = urllib3.PoolManager() r = http.request("GET", cls.URL) cls.data = json.loads(r.data) @classmethod def parse_cryptoportfolio(cls): if cls.data is None: cls.get_cryptoportfolio() def filter_weights(weight_hash): if weight_hash[1] == 0: return False if weight_hash[0] == "_row": return False return True def clean_weights(i): def clean_weights_(h): if type(h[1][i]) == str: return [h[0], h[1][i]] else: return [h[0], int(h[1][i] * 10000)] return clean_weights_ def parse_weights(portfolio_hash): # FIXME: we'll need shorts at some point assert all(map(lambda x: x == "long", portfolio_hash["holding"]["direction"])) 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: MAX_DIGITS = 18 def __init__(self, currency, value, int_val=None, linked_to=None, ticker=None): self.currency = currency if int_val is None: self._value = int(value * 10**self.MAX_DIGITS) else: self._value = int_val self.linked_to = linked_to self.ticker = ticker self.ticker_cache = {} self.ticker_cache_timestamp = time.time() @property def value(self): return self._value / 10 ** self.MAX_DIGITS def in_currency(self, other_currency, market, action="average"): if other_currency == self.currency: return self asset_ticker = self.get_ticker(other_currency, market) if asset_ticker is not None: return Amount( other_currency, 0, int_val=int(self._value * asset_ticker[action]), linked_to=self, ticker=asset_ticker) else: raise Exception("This asset is not available in the chosen market") def get_ticker(self, c2, market, refresh=False): c1 = self.currency def invert(ticker): return { "inverted": True, "average": (float(1/ticker["bid"]) + float(1/ticker["ask"]) ) / 2, "notInverted": ticker, } def augment_ticker(ticker): ticker.update({ "inverted": False, "average": (ticker["bid"] + ticker["ask"] ) / 2, }) if time.time() - self.ticker_cache_timestamp > 5: self.ticker_cache = {} self.ticker_cache_timestamp = time.time() elif not refresh: if (c1, c2, market.__class__) in self.ticker_cache: return self.ticker_cache[(c1, c2, market.__class__)] if (c2, c1, market.__class__) in self.ticker_cache: return invert(self.ticker_cache[(c2, c1, market.__class__)]) try: self.ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2)) augment_ticker(self.ticker_cache[(c1, c2, market.__class__)]) except ccxt.ExchangeError: try: self.ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1)) augment_ticker(self.ticker_cache[(c2, c1, market.__class__)]) except ccxt.ExchangeError: self.ticker_cache[(c1, c2, market.__class__)] = None return self.get_ticker(c2, market) def __abs__(self): return Amount(self.currency, 0, int_val=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, 0, int_val=self._value + other._value) def __radd__(self, other): if other == 0: return self else: return self.__add__(other) def __sub__(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, 0, int_val=self._value - other._value) def __int__(self): return self._value def __mul__(self, value): if type(value) != int and type(value) != float: raise TypeError("Amount may only be multiplied by numbers") return Amount(self.currency, 0, int_val=(self._value * value)) def __rmul__(self, value): return self.__mul__(value) def __floordiv__(self, value): if type(value) != int: raise TypeError("Amount may only be multiplied by integers") return Amount(self.currency, 0, int_val=(self._value // value)) def __truediv__(self, value): return self.__floordiv__(value) def __lt__(self, other): if self.currency != other.currency: raise Exception("Comparing amounts must be done with same currencies") return self._value < other._value 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 __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 = {} trades = {} def __init__(self, currency, total_value, free_value, used_value): self.currency = currency self.total = Amount(currency, total_value) self.free = Amount(currency, free_value) self.used = Amount(currency, used_value) @classmethod def in_currency(cls, other_currency, market, action="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, action=action) amounts[currency] = other_currency_amount return amounts @classmethod def currencies(cls): return cls.known_balances.keys() @classmethod def from_hash(cls, currency, hash_): return cls(currency, hash_["total"], hash_["free"], hash_["used"]) @classmethod def _fill_balances(cls, hash_): for key in hash_: if key in ["info", "free", "used", "total"]: continue if hash_[key]["total"] > 0: cls.known_balances[key] = cls.from_hash(key, hash_[key]) @classmethod def fetch_balances(cls, market): cls._fill_balances(market.fetch_balance()) return cls.known_balances @classmethod def dispatch_assets(cls, amount): repartition_pertenthousand = Portfolio.repartition_pertenthousand() sum_pertenthousand = sum([v for k, v in repartition_pertenthousand.items()]) amounts = {} for currency, ptt in repartition_pertenthousand.items(): amounts[currency] = ptt * amount / sum_pertenthousand if currency not in cls.known_balances: cls.known_balances[currency] = cls(currency, 0, 0, 0) return amounts @classmethod def prepare_trades(cls, market, base_currency="BTC"): cls.fetch_balances(market) values_in_base = cls.in_currency(base_currency, market) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value) Trade.compute_trades(values_in_base, new_repartition, market=market) def __int__(self): return int(self.total) def __repr__(self): return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) class Trade: 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 = [] assert self.value_from.currency == self.value_to.currency assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency self.base_currency = self.value_from.currency if market is not None: self.prepare_order(market) @classmethod def compute_trades(cls, values_in_base, new_repartition, market=None): base_currency = sum(values_in_base.values()).currency for currency in Balance.currencies(): if currency == base_currency: continue cls.trades[currency] = cls( values_in_base.get(currency, Amount(base_currency, 0)), new_repartition.get(currency, Amount(base_currency, 0)), currency, market=market ) return cls.trades @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 "buy" else: return "sell" def ticker_action(self, inverted): if self.value_from < self.value_to: return "ask" if not inverted else "bid" else: return "bid" if not inverted else "ask" def prepare_order(self, market): if self.action is None: return ticker = self.value_from.ticker inverted = ticker["inverted"] if not inverted: value_from = self.value_from.linked_to value_to = self.value_to.in_currency(self.currency, market) delta = abs(value_to - value_from) currency = self.base_currency else: ticker = ticker["notInverted"] delta = abs(self.value_to - self.value_from) currency = self.currency rate = ticker[self.ticker_action(inverted)] self.orders.append(Order(self.ticker_action(inverted), delta, rate, currency)) @classmethod def all_orders(cls): return sum(map(lambda v: v.orders, cls.trades.values()), []) @classmethod def follow_orders(cls, market): orders = cls.all_orders() finished_orders = [] while len(orders) != len(finished_orders): time.sleep(30) for order in orders: if order in finished_orders: continue if order.get_status(market) != "open": finished_orders.append(order) print("finished {}".format(order)) print("All orders finished") def __repr__(self): return "Trade({} -> {} in {}, {})".format( self.value_from, self.value_to, self.currency, self.action) class Order: DEBUG = True def __init__(self, action, amount, rate, base_currency): self.action = action self.amount = amount self.rate = rate self.base_currency = base_currency self.result = None self.status = "not run" def __repr__(self): return "Order({} {} at {} {} [{}])".format( self.action, self.amount, self.rate, self.base_currency, self.status ) def run(self, market): symbol = "{}/{}".format(self.amount.currency, self.base_currency) amount = self.amount.value if self.DEBUG: print("market.create_order('{}', 'limit', '{}', {}, price={})".format( symbol, self.action, amount, self.rate)) else: try: self.result = market.create_order(symbol, 'limit', self.action, amount, price=self.rate) self.status = "open" except Exception: pass def get_status(self, market): # other states are "closed" and "canceled" if self.status == "open": result = market.fetch_order(self.result['id']) self.status = result["status"] return self.status @static_var("cache", {}) def fetch_fees(market): if market.__class__ not in fetch_fees.cache: fetch_fees.cache[market.__class__] = market.fetch_fees() return fetch_fees.cache[market.__class__] def print_orders(market, base_currency="BTC"): Balance.prepare_trades(market, base_currency=base_currency) for currency, trade in Trade.trades.items(): print(trade) for order in trade.orders: print("\t", order, sep="") def make_orders(market, base_currency="BTC"): Balance.prepare_trades(market, base_currency=base_currency) for currency, trade in Trade.trades.items(): print(trade) for order in trade.orders: print("\t", order, sep="") order.run(market) if __name__ == '__main__': print_orders(market)