+import time
+from store import *
+from cachetools.func import ttl_cache
+
+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, balance in self.balances.all.items():
+ needed_in_margin[currency] = balance.margin_in_position - balance.margin_pending_gain
+ for trade in self.trades.pending:
+ needed_in_margin.setdefault(trade.base_currency, 0)
+ if trade.trade_type == "short":
+ needed_in_margin[trade.base_currency] -= trade.delta
+ for currency, needed in needed_in_margin.items():
+ current_balance = self.balances.all[currency].margin_available
+ moving_to_margin[currency] = (needed - current_balance)
+ delta = moving_to_margin[currency].value
+ if self.debug and delta != 0:
+ 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()
+
+ @ttl_cache(ttl=3600)
+ def fetch_fees(self):
+ return self.ccxt.fetch_fees()
+
+ @ttl_cache(maxsize=20, ttl=5)
+ def get_tickers(self, refresh=False):
+ try:
+ return self.ccxt.fetch_tickers()
+ except NotSupported:
+ return None
+
+ @ttl_cache(maxsize=20, ttl=5)
+ 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,
+ })
+
+ tickers = self.get_tickers()
+ if tickers is None:
+ try:
+ ticker = self.ccxt.fetch_ticker("{}/{}".format(c1, c2))
+ augment_ticker(ticker)
+ except ExchangeError:
+ try:
+ ticker = invert(self.ccxt.fetch_ticker("{}/{}".format(c2, c1)))
+ except ExchangeError:
+ ticker = None
+ else:
+ if "{}/{}".format(c1, c2) in tickers:
+ ticker = tickers["{}/{}".format(c1, c2)]
+ augment_ticker(ticker)
+ elif "{}/{}".format(c2, c1) in tickers:
+ ticker = invert(tickers["{}/{}".format(c2, c1)])
+ else:
+ ticker = None
+ return ticker
+
+ 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)