]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - market.py
Refactor the store to be more conciliant with multiple markets
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / market.py
index 224cc32bae3b19b51f20cc2ee2158062664f86ad..931de09e350c1a37b0bcd54baf312d433641a527 100644 (file)
--- a/market.py
+++ b/market.py
+from ccxt import ExchangeError
 import ccxt_wrapper as ccxt
-from store import ReportStore
+import time
+from store import *
 
-def get_market(config):
-    market = ccxt.poloniexE(config)
+class Market:
+    debug = False
+    ccxt = None
+    report = None
+    trades = None
+    balances = None
 
-    # For requests logging
-    market.session.origin_request = market.session.request
+    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):
+        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)
 
-    def request_wrap(self, *args, **kwargs):
-        r = self.origin_request(*args, **kwargs)
-        ReportStore.log_http_request(args[0], args[1], kwargs["data"],
-                kwargs["headers"], r)
-        return r
-    market.session.request = request_wrap.__get__(market.session, market.session.__class__)
 
-    return market