]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - market.py
Retry running order when available balance is insufficient
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / market.py
index 1601e2dce82ec6d3e2b63624bd355755bd565b8e..0cb3e67e5fc8bd1efd3542f4a06c6b0f08a46285 100644 (file)
--- a/market.py
+++ b/market.py
-import ccxt
-import decimal
-
-def exchange_sum(self, *args):
-    return sum([arg for arg in args if isinstance(arg, (float, int, decimal.Decimal))])
-ccxt.Exchange.sum = exchange_sum
-def poloniex_fetch_balance(self, params={}):
-    self.load_markets()
-    balances = self.privatePostReturnCompleteBalances(self.extend({
-        'account': 'all',
-        }, params))
-    result = {'info': balances}
-    currencies = list(balances.keys())
-    for c in range(0, len(currencies)):
-        id = currencies[c]
-        balance = balances[id]
-        currency = self.common_currency_code(id)
-        account = {
-                'free': decimal.Decimal(balance['available']),
-                'used': decimal.Decimal(balance['onOrders']),
-                'total': decimal.Decimal(0.0),
-                }
-        account['total'] = self.sum(account['free'], account['used'])
-        result[currency] = account
-    return self.parse_balance(result)
-ccxt.poloniex.fetch_balance = poloniex_fetch_balance
-
-def poloniex_fetch_balance_per_type(self):
-    balances = self.privatePostReturnAvailableAccountBalances()
-    result = {'info': balances}
-    for key, balance in balances.items():
-        result[key] = {}
-        for currency, amount in balance.items():
-            if currency not in result:
-                result[currency] = {}
-            result[currency][key] = decimal.Decimal(amount)
-            result[key][currency] = decimal.Decimal(amount)
-    return result
-ccxt.poloniex.fetch_balance_per_type = poloniex_fetch_balance_per_type
-
-def poloniex_parse_ticker(self, ticker, market=None):
-    timestamp = self.milliseconds()
-    symbol = None
-    if market:
-        symbol = market['symbol']
-    return {
-        'symbol': symbol,
-        'timestamp': timestamp,
-        'datetime': self.iso8601(timestamp),
-        'high': decimal.Decimal(ticker['high24hr']),
-        'low': decimal.Decimal(ticker['low24hr']),
-        'bid': decimal.Decimal(ticker['highestBid']),
-        'ask': decimal.Decimal(ticker['lowestAsk']),
-        'vwap': None,
-        'open': None,
-        'close': None,
-        'first': None,
-        'last': decimal.Decimal(ticker['last']),
-        'change': decimal.Decimal(ticker['percentChange']),
-        'percentage': None,
-        'average': None,
-        'baseVolume': decimal.Decimal(ticker['quoteVolume']),
-        'quoteVolume': decimal.Decimal(ticker['baseVolume']),
-        'info': ticker,
-    }
-ccxt.poloniex.parse_ticker = poloniex_parse_ticker
-
-def poloniex_create_margin_order(self, symbol, type, side, amount, price=None, lending_rate=None, params={}):
-    if type == 'market':
-        raise ccxt.ExchangeError(self.id + ' allows limit orders only')
-    self.load_markets()
-    method = 'privatePostMargin' + self.capitalize(side)
-    market = self.market(symbol)
-    price = float(price)
-    amount = float(amount)
-    if lending_rate is not None:
-        params = self.extend({"lendingRate": lending_rate}, params)
-    response = getattr(self, method)(self.extend({
-        'currencyPair': market['id'],
-        'rate': self.price_to_precision(symbol, price),
-        'amount': self.amount_to_precision(symbol, amount),
-    }, params))
-    timestamp = self.milliseconds()
-    order = self.parse_order(self.extend({
-        'timestamp': timestamp,
-        'status': 'open',
-        'type': type,
-        'side': side,
-        'price': price,
-        'amount': amount,
-    }, response), market)
-    id = order['id']
-    self.orders[id] = order
-    return self.extend({'info': response}, order)
-
-def poloniex_create_order(self, symbol, type, side, amount, price=None, account="exchange", lending_rate=None, params={}):
-    if account == "exchange":
-        return self.create_exchange_order(symbol, type, side, amount, price=price, params=params)
-    elif account == "margin":
-        return self.create_margin_order(symbol, type, side, amount, price=price, lending_rate=lending_rate, params=params)
-    else:
-        raise NotImplementedError
-ccxt.poloniex.create_exchange_order = ccxt.poloniex.create_order
-ccxt.poloniex.create_order = poloniex_create_order
-
-market = ccxt.poloniex({
-    "apiKey": "XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX",
-    "secret": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
-    })
+from ccxt import ExchangeError
+import ccxt_wrapper as ccxt
+import time
+from store import *
+
+class Market:
+    debug = False
+    ccxt = None
+    report = None
+    trades = None
+    balances = None
+
+    def __init__(self, ccxt_instance, debug=False):
+        self.debug = debug
+        self.ccxt = ccxt_instance
+        self.ccxt._market = self
+        self.report = ReportStore(self)
+        self.trades = TradeStore(self)
+        self.balances = BalanceStore(self)
+
+    @classmethod
+    def from_config(cls, config, debug=False):
+        config["apiKey"] = config.pop("key")
+
+        ccxt_instance = ccxt.poloniexE(config)
+
+        # For requests logging
+        ccxt_instance.session.origin_request = ccxt_instance.session.request
+        ccxt_instance.session._parent = ccxt_instance
+
+        def request_wrap(self, *args, **kwargs):
+            r = self.origin_request(*args, **kwargs)
+            self._parent._market.report.log_http_request(args[0],
+                    args[1], kwargs["data"], kwargs["headers"], r)
+            return r
+        ccxt_instance.session.request = request_wrap.__get__(ccxt_instance.session,
+                ccxt_instance.session.__class__)
+
+        return cls(ccxt_instance, debug=debug)
+
+    def move_balances(self):
+        needed_in_margin = {} 
+        moving_to_margin = {}
+
+        for currency in self.balances.all:
+            if self.balances.all[currency].margin_free != 0:
+                needed_in_margin[currency] = 0
+        for trade in self.trades.all:
+            if trade.value_to.currency not in needed_in_margin:
+                needed_in_margin[trade.value_to.currency] = 0
+            if trade.trade_type == "short":
+                needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
+        for currency, needed in needed_in_margin.items():
+            current_balance = self.balances.all[currency].margin_free
+            moving_to_margin[currency] = (needed - current_balance)
+            delta = moving_to_margin[currency].value
+            if self.debug:
+                self.report.log_debug_action("Moving {} from exchange to margin".format(moving_to_margin[currency]))
+                continue
+            if delta > 0:
+                self.ccxt.transfer_balance(currency, delta, "exchange", "margin")
+            elif delta < 0:
+                self.ccxt.transfer_balance(currency, -delta, "margin", "exchange")
+        self.report.log_move_balances(needed_in_margin, moving_to_margin)
+
+        self.balances.fetch_balances()
+
+    fees_cache = {}
+    def fetch_fees(self):
+        if self.ccxt.__class__ not in self.fees_cache:
+            self.fees_cache[self.ccxt.__class__] = self.ccxt.fetch_fees()
+        return self.fees_cache[self.ccxt.__class__]
+
+    ticker_cache = {}
+    ticker_cache_timestamp = time.time()
+    def get_ticker(self, c1, c2, 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() - self.ticker_cache_timestamp > 5:
+            self.ticker_cache = {}
+            self.ticker_cache_timestamp = time.time()
+        elif not refresh:
+            if (c1, c2, self.ccxt.__class__) in self.ticker_cache:
+                return self.ticker_cache[(c1, c2, self.ccxt.__class__)]
+            if (c2, c1, self.ccxt.__class__) in self.ticker_cache:
+                return invert(self.ticker_cache[(c2, c1, self.ccxt.__class__)])
+
+        try:
+            self.ticker_cache[(c1, c2, self.ccxt.__class__)] = self.ccxt.fetch_ticker("{}/{}".format(c1, c2))
+            augment_ticker(self.ticker_cache[(c1, c2, self.ccxt.__class__)])
+        except ExchangeError:
+            try:
+                self.ticker_cache[(c2, c1, self.ccxt.__class__)] = self.ccxt.fetch_ticker("{}/{}".format(c2, c1))
+                augment_ticker(self.ticker_cache[(c2, c1, self.ccxt.__class__)])
+            except ExchangeError:
+                self.ticker_cache[(c1, c2, self.ccxt.__class__)] = None
+        return self.get_ticker(c1, c2)
+
+    def follow_orders(self, sleep=None):
+        if sleep is None:
+            sleep = 7 if self.debug else 30
+            if self.debug:
+                self.report.log_debug_action("Set follow_orders tick to {}s".format(sleep))
+        tick = 0
+        self.report.log_stage("follow_orders_begin")
+        while len(self.trades.all_orders(state="open")) > 0:
+            time.sleep(sleep)
+            tick += 1
+            open_orders = self.trades.all_orders(state="open")
+            self.report.log_stage("follow_orders_tick_{}".format(tick))
+            self.report.log_orders(open_orders, tick=tick)
+            for order in open_orders:
+                if order.get_status() != "open":
+                    self.report.log_order(order, tick, finished=True)
+                else:
+                    order.trade.update_order(order, tick)
+        self.report.log_stage("follow_orders_end")
+
+    def prepare_trades(self, base_currency="BTC", liquidity="medium", compute_value="average"):
+        self.report.log_stage("prepare_trades")
+        values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
+        total_base_value = sum(values_in_base.values())
+        new_repartition = self.balances.dispatch_assets(total_base_value, liquidity=liquidity)
+        # Recompute it in case we have new currencies
+        values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
+        self.trades.compute_trades(values_in_base, new_repartition)
+
+    def update_trades(self, base_currency="BTC", liquidity="medium", compute_value="average", only=None):
+        self.report.log_stage("update_trades")
+        values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
+        total_base_value = sum(values_in_base.values())
+        new_repartition = self.balances.dispatch_assets(total_base_value, liquidity=liquidity)
+        self.trades.compute_trades(values_in_base, new_repartition, only=only)
+
+    def prepare_trades_to_sell_all(self, base_currency="BTC", compute_value="average"):
+        self.report.log_stage("prepare_trades_to_sell_all")
+        values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
+        total_base_value = sum(values_in_base.values())
+        new_repartition = self.balances.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
+        self.trades.compute_trades(values_in_base, new_repartition)
+