Exemplo n.º 1
0
    def __init__(self, **kwargs):
        """Initialize."""
        e = None
        if not kwargs.get("account"):
            e = Error("param account miss")
        if not kwargs.get("strategy"):
            e = Error("param strategy miss")
        if not kwargs.get("symbol"):
            e = Error("param symbol miss")
        if not kwargs.get("contract_type"):
            e = Error("param contract_type miss")
        if not kwargs.get("host"):
            kwargs["host"] = "https://api.hbdm.com"
        if not kwargs.get("wss"):
            kwargs["wss"] = "wss://api.hbdm.com"
        if not kwargs.get("access_key"):
            e = Error("param access_key miss")
        if not kwargs.get("secret_key"):
            e = Error("param secret_key miss")
        if e:
            logger.error(e, caller=self)
            if kwargs.get("init_success_callback"):
                SingleTask.run(kwargs["init_success_callback"], False, e)
            return

        self._account = kwargs["account"]
        self._strategy = kwargs["strategy"]
        self._platform = HUOBI_FUTURE
        self._symbol = kwargs["symbol"]
        self._contract_type = kwargs["contract_type"]
        self._host = kwargs["host"]
        self._wss = kwargs["wss"]
        self._access_key = kwargs["access_key"]
        self._secret_key = kwargs["secret_key"]
        self._asset_update_callback = kwargs.get("asset_update_callback")
        self._order_update_callback = kwargs.get("order_update_callback")
        self._position_update_callback = kwargs.get("position_update_callback")
        self._init_success_callback = kwargs.get("init_success_callback")

        url = self._wss + "/notification"
        self._ws = Websocket(url, self.connected_callback, process_binary_callback=self.process_binary)
        self._ws.initialize()

        LoopRunTask.register(self.send_heartbeat_msg, 5)

        self._assets = {}  # Asset detail, {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }.
        self._orders = {}  # Order objects, {"order_id": order, ...}.
        self._position = Position(self._platform, self._account, self._strategy, self._symbol + '/' + self._contract_type)

        self._order_channel = "orders.{symbol}".format(symbol=self._symbol.lower())
        self._position_channel = "positions.{symbol}".format(symbol=self._symbol.lower())

        self._subscribe_order_ok = False
        self._subscribe_position_ok = False

        self._rest_api = HuobiFutureRestAPI(self._host, self._access_key, self._secret_key)

        # Subscribe AssetEvent.
        if self._asset_update_callback:
            AssetSubscribe(self._platform, self._account, self.on_event_asset_update)
Exemplo n.º 2
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = None
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = None
        self._secret_key = None
        self._last_msg_ts = tools.get_cur_timestamp_ms()  # 上次接收到消息的时间戳

        for item in config.accounts:
            if item["platform"] == self._platform:
                self._wss = item.get("wss", "wss://deribit.com")
                self._access_key = item["access_key"]
                self._secret_key = item["secret_key"]
        if not self._wss or not self._access_key or not self._access_key:
            logger.error(
                "no find deribit account in ACCOUNTS from config file.",
                caller=self)
            return

        url = self._wss + "/ws/api/v1/"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 10)
Exemplo n.º 3
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://real.okex.com:10442")
        self._host = kwargs.get("host", "https://www.okex.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = kwargs.get("access_key")
        self._secret_key = kwargs.get("secret_key")
        self._passphrase = kwargs.get("passphrase")

        self._orderbooks = {
        }  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}

        url = self._wss + "/ws/v3"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 5)

        self._rest_api = OKExFutureRestAPI(self._host, self._access_key,
                                           self._secret_key, self._passphrase)

        self._initialize()
Exemplo n.º 4
0
    def __init__(self, **kwargs):
        """ 初始化
        """
        e = None
        if not kwargs.get("account"):
            e = Error("param account miss")
        if not kwargs.get("strategy"):
            e = Error("param strategy miss")
        if not kwargs.get("symbol"):
            e = Error("param symbol miss")
        if not kwargs.get("host"):
            kwargs["host"] = "https://api.huobi.pro"
        if not kwargs.get("wss"):
            kwargs["wss"] = "wss://api.huobi.pro"
        if not kwargs.get("access_key"):
            e = Error("param access_key miss")
        if not kwargs.get("secret_key"):
            e = Error("param secret_key miss")
        if e:
            logger.error(e, caller=self)
            SingleTask.run(kwargs["init_success_callback"], False, e)
            return

        self._account = kwargs["account"]
        self._strategy = kwargs["strategy"]
        self._platform = HUOBI
        self._symbol = kwargs["symbol"]
        self._host = kwargs["host"]
        self._wss = kwargs["wss"]
        self._access_key = kwargs["access_key"]
        self._secret_key = kwargs["secret_key"]
        self._asset_update_callback = kwargs.get("asset_update_callback")
        self._order_update_callback = kwargs.get("order_update_callback")
        self._init_success_callback = kwargs.get("init_success_callback")

        self._raw_symbol = self._symbol.replace("/",
                                                "").lower()  # 转换成交易所对应的交易对格式
        self._order_channel = "orders.{}".format(self._raw_symbol)  # 订阅订单更新频道

        url = self._wss + "/ws/v1"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

        self._assets = {
        }  # 资产 {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }
        self._orders = {}  # 订单

        # 初始化 REST API 对象
        self._rest_api = HuobiRestAPI(self._host, self._access_key,
                                      self._secret_key)

        # 初始化资产订阅
        if self._asset_update_callback:
            AssetSubscribe(self._platform, self._account,
                           self.on_event_asset_update)
Exemplo n.º 5
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.bitmex.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")

        self._c_to_s = {}  # {"channel": "symbol"}
        url = self._wss + "/realtime"
        self._ws = Websocket(url, self.connected_callback, process_callback=self.process)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_message, 30)
Exemplo n.º 6
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.hbdm.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._c_to_s = {}  # {"channel": "symbol"}

        url = self._wss + "/ws"
        self._ws = Websocket(url, self.connected_callback, process_binary_callback=self.process_binary)
        self._ws.initialize()
Exemplo n.º 7
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://stream.binance.com:9443")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._c_to_s = {}
        self._tickers = {}

        url = self._make_url()
        self._ws = Websocket(url, process_callback=self.process)
        self._ws.initialize()
Exemplo n.º 8
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://real.okex.com:8443")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._orderbooks = {}  # 订单薄数据 {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}

        url = self._wss + "/ws/v3"
        self._ws = Websocket(url, connected_callback=self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 5)
Exemplo n.º 9
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://ws.gate.io")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._orderbook_price_precious = kwargs.get("orderbook_price_precious", "0.00000001")

        self._request_id = 0  # unique request id for per request message.
        self._orderbooks = {}  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}, "timestamp": 0}, ... }

        url = self._wss + "/v3/"
        self._ws = Websocket(url, connected_callback=self.connected_callback, process_callback=self.process)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 10)
Exemplo n.º 10
0
 async def _init_websocket(self):
     """ Initialize Websocket connection.
     """
     # Get listen key first.
     success, error = await self._rest_api.get_listen_key()
     if error:
         e = Error("get listen key failed: {}".format(error))
         logger.error(e, caller=self)
         SingleTask.run(self._init_success_callback, False, e)
         return
     self._listen_key = success["listenKey"]
     uri = "/ws/" + self._listen_key
     url = urljoin(self._wss, uri)
     self._ws = Websocket(url,
                          self.connected_callback,
                          process_callback=self.process)
     self._ws.initialize()
Exemplo n.º 11
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://ws-feed.pro.coinbase.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._orderbooks = {
        }  # orderbook datas {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}
        self._symbols_map = {
        }  # config symbol name to raw symbol name, e.g. {"BTC-USD": "BTC/USD"}

        url = self._wss
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()
Exemplo n.º 12
0
 async def _initialize(self):
     """Initialize."""
     for channel in self._channels:
         if channel == "orderbook":
             LoopRunTask.register(self.do_orderbook_update,
                                  self._orderbook_interval)
         elif channel == "trade":
             # Create Websocket connection.
             success, error = await self._rest_api.get_websocket_token()
             if error:
                 logger.error("get websocket token error!", caller=self)
                 return
             url = "{}?token={}".format(
                 success["instanceServers"][0]["endpoint"],
                 success["token"])
             self._ws = Websocket(url, self.connected_callback,
                                  self.process)
             self._ws.initialize()
             LoopRunTask.register(self.send_heartbeat_msg, 30)
Exemplo n.º 13
0
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.hbdm.com")
        self._host = kwargs.get("host", "https://api.hbdm.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = kwargs.get("access_key", "")
        self._secret_key = kwargs.get("secret_key", "")

        self._c_to_s = {}  # {"channel": "symbol"}

        url = self._wss + "/ws"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

        self._rest_api = HuobiFutureRestAPI(self._host, self._access_key,
                                            self._secret_key)
        self._initialize()
Exemplo n.º 14
0
    def __init__(self, **kwargs):
        """Initialize."""
        self._platform = kwargs["platform"]
        self._host = kwargs.get("host", "https://api.kraken.com")
        self._wss = kwargs.get("wss", "wss://ws.kraken.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_interval = kwargs.get("orderbook_interval", 2)
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self.heartbeat_msg = {}
        self._orderbooks = {
        }  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}, "timestamp": 0}, ... }

        self._rest_api = KrakenRestAPI(self._host, None, None)

        url = self._wss
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

        # Register a loop run task to reset heartbeat message request id.
        LoopRunTask.register(self.send_heartbeat_msg, 5)
Exemplo n.º 15
0
class HuobiFutureTrade:
    """ Huobi Future Trade module. You can initialize trade object with some attributes in kwargs.

    Attributes:
        account: Account name for this trade exchange.
        strategy: What's name would you want to created for you strategy.
        symbol: Symbol name for your trade.
        host: HTTP request host. default `https://api.hbdm.com"`.
        wss: Websocket address. default `wss://www.hbdm.com`.
        access_key: Account's ACCESS KEY.
        secret_key Account's SECRET KEY.
        asset_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `asset_update_callback` is like `async def on_asset_update_callback(asset: Asset): pass` and this
            callback function will be executed asynchronous when received AssetEvent.
        order_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `order_update_callback` is like `async def on_order_update_callback(order: Order): pass` and this
            callback function will be executed asynchronous when some order state updated.
        position_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `position_update_callback` is like `async def on_position_update_callback(order: Position): pass` and
            this callback function will be executed asynchronous when some position state updated.
        init_success_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `init_success_callback` is like `async def on_init_success_callback(success: bool, error: Error, **kwargs): pass`
            and this callback function will be executed asynchronous after Trade module object initialized successfully.
    """
    def __init__(self, **kwargs):
        """Initialize."""
        e = None
        if not kwargs.get("account"):
            e = Error("param account miss")
        if not kwargs.get("strategy"):
            e = Error("param strategy miss")
        if not kwargs.get("symbol"):
            e = Error("param symbol miss")
        if not kwargs.get("contract_type"):
            e = Error("param contract_type miss")
        if not kwargs.get("contract_code"):
            e = Error("param contract_code miss")
        if not kwargs.get("host"):
            kwargs["host"] = "https://api.hbdm.com"
        if not kwargs.get("wss"):
            kwargs["wss"] = "wss://api.hbdm.com"
        if not kwargs.get("access_key"):
            e = Error("param access_key miss")
        if not kwargs.get("secret_key"):
            e = Error("param secret_key miss")
        if e:
            logger.error(e, caller=self)
            if kwargs.get("init_success_callback"):
                SingleTask.run(kwargs["init_success_callback"], False, e)
            return

        self._account = kwargs["account"]
        self._strategy = kwargs["strategy"]
        self._platform = HUOBI_FUTURE
        self._symbol = kwargs["symbol"]
        self._contract_type = kwargs["contract_type"]
        self._contract_code = kwargs["contract_code"]
        self._host = kwargs["host"]
        self._wss = kwargs["wss"]
        self._access_key = kwargs["access_key"]
        self._secret_key = kwargs["secret_key"]
        self._asset_update_callback = kwargs.get("asset_update_callback")
        self._order_update_callback = kwargs.get("order_update_callback")
        self._position_update_callback = kwargs.get("position_update_callback")
        self._init_success_callback = kwargs.get("init_success_callback")

        url = self._wss + "/notification"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

        self._assets = {
        }  # Asset detail, {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }.
        self._orders = {}  # Order objects, {"order_id": order, ...}.
        self._position = Position(self._platform, self._account,
                                  self._strategy, self._contract_code)

        self._order_channel = "orders.{symbol}".format(
            symbol=self._symbol.lower())
        self._position_channel = "positions.{symbol}".format(
            symbol=self._symbol.lower())

        self._subscribe_order_ok = False
        self._subscribe_position_ok = False

        self._rest_api = HuobiFutureRestAPI(self._host, self._access_key,
                                            self._secret_key)

        # Subscribe AssetEvent.
        if self._asset_update_callback:
            AssetSubscribe(self._platform, self._account,
                           self.on_event_asset_update)

    @property
    def assets(self):
        return copy.copy(self._assets)

    @property
    def orders(self):
        return copy.copy(self._orders)

    @property
    def position(self):
        return copy.copy(self._position)

    @property
    def rest_api(self):
        return self._rest_api

    async def connected_callback(self):
        """After connect to Websocket server successfully, send a auth message to server."""
        timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
        data = {
            "AccessKeyId": self._access_key,
            "SignatureMethod": "HmacSHA256",
            "SignatureVersion": "2",
            "Timestamp": timestamp
        }
        sign = self._rest_api.generate_signature("GET", data, "/notification")
        data["op"] = "auth"
        data["type"] = "api"
        data["Signature"] = sign
        await self._ws.send(data)

    async def auth_callback(self, data):
        if data["err-code"] != 0:
            e = Error(
                "Websocket connection authorized failed: {}".format(data))
            logger.error(e, caller=self)
            SingleTask.run(self._init_success_callback, False, e)
            return

        # subscribe order
        data = {
            "op": "sub",
            "cid": tools.get_uuid1(),
            "topic": self._order_channel
        }
        await self._ws.send(data)

        # subscribe position
        data = {
            "op": "sub",
            "cid": tools.get_uuid1(),
            "topic": self._position_channel
        }
        await self._ws.send(data)

    async def sub_callback(self, data):
        if data["err-code"] != 0:
            e = Error("subscribe {} failed!".format(data["topic"]))
            logger.error(e, caller=self)
            SingleTask.run(self._init_success_callback, False, e)
            return
        if data["topic"] == self._order_channel:
            self._subscribe_order_ok = True
        elif data["topic"] == self._position_channel:
            self._subscribe_position_ok = True
        if self._subscribe_order_ok and self._subscribe_position_ok:
            success, error = await self._rest_api.get_open_orders(self._symbol)
            if error:
                e = Error("get open orders failed!")
                SingleTask.run(self._init_success_callback, False, e)
            for order_info in success["data"]["orders"]:
                order_info["ts"] = order_info["created_at"]
                self._update_order(order_info)
            SingleTask.run(self._init_success_callback, True, None)

    @async_method_locker("HuobiFutureTrade.process_binary.locker")
    async def process_binary(self, raw):
        """ 处理websocket上接收到的消息
        @param raw 原始的压缩数据
        """
        data = json.loads(gzip.decompress(raw).decode())
        logger.debug("data:", data, caller=self)

        op = data.get("op")
        if op == "ping":
            hb_msg = {"op": "pong", "ts": data.get("ts")}
            await self._ws.send(hb_msg)

        elif op == "auth":
            await self.auth_callback(data)

        elif op == "sub":
            await self.sub_callback(data)

        elif op == "notify":
            if data["topic"] == self._order_channel:
                self._update_order(data)
            elif data["topic"] == "positions":
                self._update_position(data)
            elif data["topic"] == self._position_channel:
                self._update_position(data)

    async def create_order(self,
                           action,
                           price,
                           quantity,
                           order_type=ORDER_TYPE_LIMIT,
                           *args,
                           **kwargs):
        """ Create an order.

        Args:
            action: Trade direction, BUY or SELL.
            price: Price of each contract.
            quantity: The buying or selling quantity.
            order_type: Order type, LIMIT or MARKET.
            kwargs:
                lever_rate: Leverage rate, 10 or 20.

        Returns:
            order_no: Order ID if created successfully, otherwise it's None.
            error: Error information, otherwise it's None.
        """
        if int(quantity) > 0:
            if action == ORDER_ACTION_BUY:
                direction = "buy"
                offset = "open"
            elif action == ORDER_ACTION_SELL:
                direction = "sell"
                offset = "close"
            else:
                return None, "action error"
        else:
            if action == ORDER_ACTION_BUY:
                direction = "buy"
                offset = "close"
            elif action == ORDER_ACTION_SELL:
                direction = "sell"
                offset = "open"
            else:
                return None, "action error"

        lever_rate = kwargs.get("lever_rate", 20)
        if order_type == ORDER_TYPE_LIMIT:
            order_price_type = "limit"
        elif order_type == ORDER_TYPE_MARKET:
            order_price_type = "opponent"
        else:
            return None, "order type error"

        quantity = abs(int(quantity))
        result, error = await self._rest_api.create_order(
            self._symbol, self._contract_type, self._contract_code, price,
            quantity, direction, offset, lever_rate, order_price_type)
        if error:
            return None, error
        return str(result["data"]["order_id"]), None

    async def revoke_order(self, *order_nos):
        """ Revoke (an) order(s).

        Args:
            order_nos: Order id list, you can set this param to 0 or multiple items. If you set 0 param, you can cancel
                all orders for this symbol(initialized in Trade object). If you set 1 param, you can cancel an order.
                If you set multiple param, you can cancel multiple orders. Do not set param length more than 100.

        Returns:
            Success or error, see bellow.
        """
        # If len(order_nos) == 0, you will cancel all orders for this symbol(initialized in Trade object).
        if len(order_nos) == 0:
            success, error = await self._rest_api.revoke_order_all(
                self._symbol, self._contract_code, self._contract_type)
            if error:
                return False, error
            if success.get("errors"):
                return False, success["errors"]
            return True, None

        # If len(order_nos) == 1, you will cancel an order.
        if len(order_nos) == 1:
            success, error = await self._rest_api.revoke_order(
                self._symbol, order_nos[0])
            if error:
                return order_nos[0], error
            if success.get("errors"):
                return False, success["errors"]
            else:
                return order_nos[0], None

        # If len(order_nos) > 1, you will cancel multiple orders.
        if len(order_nos) > 1:
            success, error = await self._rest_api.revoke_orders(
                self._symbol, order_nos)
            if error:
                return order_nos[0], error
            if success.get("errors"):
                return False, success["errors"]
            return success, error

    async def get_open_order_nos(self):
        """ Get open order id list.

        Args:
            None.

        Returns:
            order_nos: Open order id list, otherwise it's None.
            error: Error information, otherwise it's None.
        """
        success, error = await self._rest_api.get_open_orders(self._symbol)
        if error:
            return None, error
        else:
            order_nos = []
            for order_info in success["data"]["orders"]:
                if order_info["contract_code"] != self._contract_code:
                    continue
                order_nos.append(str(order_info["order_id"]))
            return order_nos, None

    def _update_order(self, order_info):
        """ Order update.

        Args:
            order_info: Order information.
        """
        if order_info["contract_code"] != self._contract_code:
            return
        order_no = str(order_info["order_id"])
        status = order_info["status"]

        order = self._orders.get(order_no)
        if not order:
            if order_info["direction"] == "buy":
                if order_info["offset"] == "open":
                    trade_type = TRADE_TYPE_BUY_OPEN
                else:
                    trade_type = TRADE_TYPE_BUY_CLOSE
            else:
                if order_info["offset"] == "close":
                    trade_type = TRADE_TYPE_SELL_CLOSE
                else:
                    trade_type = TRADE_TYPE_SELL_OPEN

            info = {
                "platform":
                self._platform,
                "account":
                self._account,
                "strategy":
                self._strategy,
                "order_no":
                order_no,
                "action":
                ORDER_ACTION_BUY
                if order_info["direction"] == "buy" else ORDER_ACTION_SELL,
                "symbol":
                self._contract_code,
                "price":
                order_info["price"],
                "quantity":
                order_info["volume"],
                "trade_type":
                trade_type
            }
            order = Order(**info)
            self._orders[order_no] = order

        if status in [1, 2, 3]:
            order.status = ORDER_STATUS_SUBMITTED
        elif status == 4:
            order.status = ORDER_STATUS_PARTIAL_FILLED
            order.remain = int(order.quantity) - int(
                order_info["trade_volume"])
        elif status == 6:
            order.status = ORDER_STATUS_FILLED
            order.remain = 0
        elif status in [5, 7]:
            order.status = ORDER_STATUS_CANCELED
            order.remain = int(order.quantity) - int(
                order_info["trade_volume"])
        else:
            return

        order.avg_price = order_info["trade_avg_price"]
        order.ctime = order_info["created_at"]
        order.utime = order_info["ts"]

        SingleTask.run(self._order_update_callback, copy.copy(order))

        # Delete order that already completed.
        if order.status in [
                ORDER_STATUS_FAILED, ORDER_STATUS_CANCELED, ORDER_STATUS_FILLED
        ]:
            self._orders.pop(order_no)

    def _update_position(self, data):
        """ Position update.

        Args:
            position_info: Position information.

        Returns:
            None.
        """
        for position_info in data["data"]:
            if position_info["contract_code"] != self._contract_code:
                return
            if position_info["direction"] == "buy":
                self._position.long_quantity = int(position_info["volume"])
                self._position.long_avg_price = position_info["cost_hold"]
            else:
                self._position.short_quantity = int(position_info["volume"])
                self._position.short_avg_price = position_info["cost_hold"]
            # self._position.liquid_price = None
            self._position.utime = data["ts"]
            SingleTask.run(self._position_update_callback,
                           copy.copy(self._position))

    async def on_event_asset_update(self, asset: Asset):
        """ Asset event data callback.

        Args:
            asset: Asset object callback from EventCenter.

        Returns:
            None.
        """
        self._assets = asset
        SingleTask.run(self._asset_update_callback, asset)
Exemplo n.º 16
0
class GateMarket:
    """ Gate.io Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `gate`.
            wss: Wss host, default is `wss://ws.gate.io`.
            symbols: Trade pair list, e.g. ["ETH/BTC"].
            channels: What are channels to be subscribed, only support `orderbook` / `trade` / `kline`.
            orderbook_length: The length of orderbook"s data to be published via OrderbookEvent, default is 10.
    """

    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://ws.gate.io")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._orderbook_price_precious = kwargs.get("orderbook_price_precious", "0.00000001")

        self._request_id = 0  # unique request id for per request message.
        self._orderbooks = {}  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}, "timestamp": 0}, ... }

        url = self._wss + "/v3/"
        self._ws = Websocket(url, connected_callback=self.connected_callback, process_callback=self.process)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 10)

    async def connected_callback(self):
        """After create Websocket connection successfully, we will subscribing orderbook/trade/kline events."""
        if not self._symbols:
            logger.warn("symbols not found in config file.", caller=self)
            return
        if not self._channels:
            logger.warn("channels not found in config file.", caller=self)
            return
        for ch in self._channels:
            if ch == "trade":
                params = []
                for s in self._symbols:
                    params.append(s.replace("/", "_"))
                request_id = await self._generate_request_id()
                d = {"id": request_id, "method": "trades.subscribe", "params": params}
                await self._ws.send(d)
                logger.info("subscribe trade success.", caller=self)
            elif ch == "orderbook":
                await self.subscribe_orderbook()
            elif ch == "kline":
                for s in self._symbols:
                    params = [s.replace("/", "_"), 60]
                    request_id = await self._generate_request_id()
                    d = {"id": request_id, "method": "kline.subscribe", "params": params}
                    await self._ws.send(d)
                    logger.info("subscribe kline success.", caller=self)
            else:
                logger.error("channel error:", ch, caller=self)
                continue

    async def subscribe_orderbook(self):
        self._orderbooks = {}
        params = []
        for s in self._symbols:
            params.append([s.replace("/", "_"), int(self._orderbook_length), str(self._orderbook_price_precious)])
        request_id = await self._generate_request_id()
        d = {"id": request_id, "method": "depth.subscribe", "params": params}
        await self._ws.send(d)
        logger.info("subscribe orderbook success.", caller=self)

    async def unsubscribe_orderbook(self):
        request_id = await self._generate_request_id()
        d = {"id": request_id, "method": "depth.unsubscribe", "params": []}
        await self._ws.send(d)
        logger.info("unsubscribe orderbook success.", caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        request_id = await self._generate_request_id()
        data = {
            "id": request_id,
            "method": "server.ping",
            "params": []
        }
        if not self._ws:
            logger.error("Websocket connection not yeah!", caller=self)
            return
        await self._ws.send(data)

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message data that received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)

        if not isinstance(msg, dict):
            return

        method = msg.get("method")
        if method == "trades.update":
            await self.process_trade_update(msg["params"])
        elif method == "depth.update":
            await self.process_orderbook_update(msg["params"])
        elif method == "kline.update":
            await self.process_kline_update(msg["params"])

    async def process_trade_update(self, data):
        """ Deal with trade data, and publish trade message to EventCenter via TradeEvent.

        Args:
            data: Newest trade data.
        """
        symbol = data[0].replace("_", "/")
        for item in data[1]:
            action = ORDER_ACTION_BUY if item["type"] == "buy" else ORDER_ACTION_SELL
            price = "%.8f" % float(item["price"])
            quantity = "%.8f" % float(item["amount"])
            timestamp = int(item["time"] * 1000)

            # Publish EventTrade.
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action": action,
                "price": price,
                "quantity": quantity,
                "timestamp": timestamp
            }
            EventTrade(**trade).publish()
            logger.info("symbol:", symbol, "trade:", trade, caller=self)

    @async_method_locker("process_orderbook_update")
    async def process_orderbook_update(self, data):
        """ Deal with orderbook message that updated.

        Args:
            data: Newest orderbook data.
        """
        symbol = data[2].replace("_", "/")
        if symbol not in self._symbols:
            return
        if symbol not in self._orderbooks:
            self._orderbooks[symbol] = {"asks": {}, "bids": {}, "timestamp": 0}

        for item in data[1].get("asks", []):
            price = float(item[0])
            quantity = float(item[1])
            if quantity == 0:
                if price in self._orderbooks[symbol]["asks"]:
                    self._orderbooks[symbol]["asks"].pop(price)
            else:
                self._orderbooks[symbol]["asks"][price] = quantity
        for item in data[1].get("bids", []):
            price = float(item[0])
            quantity = float(item[1])
            if quantity == 0:
                if price in self._orderbooks[symbol]["bids"]:
                    self._orderbooks[symbol]["bids"].pop(price)
            else:
                self._orderbooks[symbol]["bids"][price] = quantity
        self._orderbooks[symbol]["timestamp"] = tools.get_cur_timestamp_ms()

        await self.publish_orderbook(symbol)

    async def publish_orderbook(self, symbol):
        """ Publish OrderbookEvent.
        """
        ob = copy.copy(self._orderbooks[symbol])
        if not ob["asks"] or not ob["bids"]:
            logger.warn("symbol:", symbol, "asks:", ob["asks"], "bids:", ob["bids"], caller=self)
            return

        ask_keys = sorted(list(ob["asks"].keys()))
        bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:", symbol, "ask1:", ask_keys[0], "bid1:", bid_keys[0], caller=self)
            await self.unsubscribe_orderbook()
            await self.subscribe_orderbook()
            return

        # asks
        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["asks"].get(k)
            asks.append([price, quantity])

        # bids
        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["bids"].get(k)
            bids.append([price, quantity])

        # Publish OrderbookEvent.
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": ob["timestamp"]
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_kline_update(self, data):
        """ Deal with 1min kline data, and publish kline message to EventCenter via KlineEvent.

        Args:
            data: Newest kline data.
        """
        for item in data:
            timestamp = item[0] * 1000
            _open = "%.8f" % float(item[1])
            close = "%.8f" % float(item[2])
            high = "%.8f" % float(item[3])
            low = "%.8f" % float(item[4])
            volume = "%.8f" % float(item[6])
            symbol = item[-1].replace("_", "/")

            # Publish EventKline
            kline = {
                "platform": self._platform,
                "symbol": symbol,
                "open": _open,
                "high": high,
                "low": low,
                "close": close,
                "volume": volume,
                "timestamp": timestamp,
                "kline_type": const.MARKET_TYPE_KLINE
            }
            EventKline(**kline).publish()
            logger.info("symbol:", symbol, "kline:", kline, caller=self)

    @async_method_locker("_generate_request_id")
    async def _generate_request_id(self):
        self._request_id += 1
        return self._request_id
Exemplo n.º 17
0
class GeminiMarket:
    """ Gemini Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `gemini`.
            wss: Exchange Websocket host address, default is "wss://api.gemini.com".
            symbols: Trade pair list, e.g. ["ETH/BTC"].
            channels: channel list, only `orderbook` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://api.gemini.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._orderbooks = {
        }  # orderbook datas {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}
        self._symbols_map = {
        }  # config symbol name to raw symbol name, e.g. {"BTCUSD": "BTC/USD"}

        url = self._wss + "/v2/marketdata"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

    async def connected_callback(self):
        """After create Websocket connection successfully, we will subscribing orderbook events."""
        symbols = []
        for s in self._symbols:
            t = s.replace("/", "")
            symbols.append(t)
            self._symbols_map[t] = s

        if not symbols:
            logger.warn("symbols not found in config file.", caller=self)
            return
        if not self._channels:
            logger.warn("channels not found in config file.", caller=self)
            return

        subscriptions = []
        for ch in self._channels:
            if ch == "orderbook":
                sub = {"name": "l2", "symbols": symbols}
                subscriptions.append(sub)
            else:
                logger.error("channel error! channel:", ch, caller=self)
        if subscriptions:
            msg = {"type": "subscribe", "subscriptions": subscriptions}
            await self._ws.send(msg)
            logger.info("subscribe orderbook success.", caller=self)

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message data that received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)

        t = msg.get("type")
        if t == "l2_updates":
            symbol = msg["symbol"]
            datas = msg["changes"]
            await self.process_orderbook_update(symbol, datas)

    @async_method_locker("process_orderbook_update")
    async def process_orderbook_update(self, symbol_raw, datas):
        """ Deal with orderbook message that updated.

        Args:
            symbol_raw: Trade pair raw name.
            datas: Newest orderbook data.
        """
        if symbol_raw not in self._symbols_map:
            return
        symbol = self._symbols_map[symbol_raw]
        if symbol not in self._orderbooks:
            self._orderbooks[symbol] = {"asks": {}, "bids": {}}

        for item in datas:
            side = item[0]
            price = float(item[1])
            quantity = float(item[2])
            if side == "sell":
                if quantity == 0:
                    if price in self._orderbooks[symbol]["asks"]:
                        self._orderbooks[symbol]["asks"].pop(price)
                else:
                    self._orderbooks[symbol]["asks"][price] = quantity
            elif side == "buy":
                if quantity == 0:
                    if price in self._orderbooks[symbol]["bids"]:
                        self._orderbooks[symbol]["bids"].pop(price)
                else:
                    self._orderbooks[symbol]["bids"][price] = quantity

        await self.publish_orderbook_event(symbol)

    async def publish_orderbook_event(self, symbol):
        """Publish OrderbookEvent."""
        ob = copy.copy(self._orderbooks[symbol])
        if not ob["asks"] or not ob["bids"]:
            logger.warn("symbol:",
                        symbol,
                        "asks:",
                        ob["asks"],
                        "bids:",
                        ob["bids"],
                        caller=self)
            return

        ask_keys = sorted(list(ob["asks"].keys()))
        bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:",
                        symbol,
                        "ask1:",
                        ask_keys[0],
                        "bid1:",
                        bid_keys[0],
                        caller=self)
            return

        # asks
        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["asks"].get(k)
            asks.append([price, quantity])

        # bids
        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["bids"].get(k)
            bids.append([price, quantity])

        # Publish OrderbookEvent.
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": tools.get_cur_timestamp_ms()
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)
Exemplo n.º 18
0
class OKExFuture:
    """ OKEx Future Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `okex_future`.
            wss: Exchange Websocket host address, default is "wss://real.okex.com:10442".
            symbols: symbol list, OKEx Future instrument_id list.
            channels: channel list, only `orderbook` , `kline` and `trade` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://real.okex.com:10442")
        self._host = kwargs.get("host", "https://www.okex.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = kwargs.get("access_key")
        self._secret_key = kwargs.get("secret_key")
        self._passphrase = kwargs.get("passphrase")

        self._orderbooks = {
        }  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}

        url = self._wss + "/ws/v3"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 5)

        self._rest_api = OKExFutureRestAPI(self._host, self._access_key,
                                           self._secret_key, self._passphrase)

        self._initialize()

    async def connected_callback(self):
        """After create connection to Websocket server successfully, we will subscribe orderbook/kline/trade event."""
        ches = []
        for ch in self._channels:
            if ch == "orderbook":
                for symbol in self._symbols:
                    ch = "futures/depth:{s}".format(s=symbol)
                    ches.append(ch)
            elif ch == "trade":
                for symbol in self._symbols:
                    ch = "futures/trade:{s}".format(s=symbol.replace("/", '-'))
                    ches.append(ch)
            elif ch == "kline":
                for symbol in self._symbols:
                    ch = "futures/candle60s:{s}".format(
                        s=symbol.replace("/", '-'))
                    ches.append(ch)
            # else:
            #     logger.error("channel error! channel:", ch, caller=self)
            if len(ches) > 0:
                msg = {"op": "subscribe", "args": ches}
                await self._ws.send(msg)
                logger.info("subscribe orderbook/kline/trade success.",
                            caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        data = "ping"
        if not self._ws:
            logger.error("Websocket connection not yeah!", caller=self)
            return
        await self._ws.send(data)

    async def process_binary(self, raw):
        """ Process message that received from Websocket connection.

        Args:
            raw: Raw binary message received from Websocket connection.
        """
        decompress = zlib.decompressobj(-zlib.MAX_WBITS)
        msg = decompress.decompress(raw)
        msg += decompress.flush()
        msg = msg.decode()
        if msg == "pong":  # Heartbeat message.
            return
        msg = json.loads(msg)
        # logger.debug("msg:", msg, caller=self)

        table = msg.get("table")
        if table == "futures/depth":
            if msg.get("action") == "partial":
                for d in msg["data"]:
                    await self.process_orderbook_partial(d)
            elif msg.get("action") == "update":
                for d in msg["data"]:
                    await self.process_orderbook_update(d)
        elif table == "futures/trade":
            for d in msg["data"]:
                await self.process_trade(d)
        elif table == "futures/candle60s":
            for d in msg["data"]:
                await self.process_kline(d)

    async def process_orderbook_partial(self, data):
        """Deal with orderbook partial message."""
        symbol = data.get("instrument_id")
        if symbol not in self._symbols:
            return
        asks = data.get("asks")
        bids = data.get("bids")
        self._orderbooks[symbol] = {"asks": {}, "bids": {}, "timestamp": 0}
        for ask in asks:
            price = float(ask[0])
            quantity = int(ask[1])
            self._orderbooks[symbol]["asks"][price] = quantity
        for bid in bids:
            price = float(bid[0])
            quantity = int(bid[1])
            self._orderbooks[symbol]["bids"][price] = quantity
        timestamp = tools.utctime_str_to_mts(data.get("timestamp"))
        self._orderbooks[symbol]["timestamp"] = timestamp

    async def process_orderbook_update(self, data):
        """Deal with orderbook update message."""
        symbol = data.get("instrument_id")
        asks = data.get("asks")
        bids = data.get("bids")
        timestamp = tools.utctime_str_to_mts(data.get("timestamp"))

        if symbol not in self._orderbooks:
            return
        self._orderbooks[symbol]["timestamp"] = timestamp

        for ask in asks:
            price = float(ask[0])
            quantity = int(ask[1])
            if quantity == 0 and price in self._orderbooks[symbol]["asks"]:
                self._orderbooks[symbol]["asks"].pop(price)
            else:
                self._orderbooks[symbol]["asks"][price] = quantity

        for bid in bids:
            price = float(bid[0])
            quantity = int(bid[1])
            if quantity == 0 and price in self._orderbooks[symbol]["bids"]:
                self._orderbooks[symbol]["bids"].pop(price)
            else:
                self._orderbooks[symbol]["bids"][price] = quantity

        await self.publish_orderbook(symbol)

    async def publish_orderbook(self, symbol):
        """Publish orderbook message to EventCenter via OrderbookEvent."""
        ob = copy.copy(self._orderbooks[symbol])
        if not ob["asks"] or not ob["bids"]:
            logger.warn("symbol:",
                        symbol,
                        "asks:",
                        ob["asks"],
                        "bids:",
                        ob["bids"],
                        caller=self)
            return

        ask_keys = sorted(list(ob["asks"].keys()))
        bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:",
                        symbol,
                        "ask1:",
                        ask_keys[0],
                        "bid1:",
                        bid_keys[0],
                        caller=self)
            return

        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = str(ob["asks"].get(k))
            asks.append([price, quantity])

        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = str(ob["bids"].get(k))
            bids.append([price, quantity])

        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": ob["timestamp"]
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_trade(self, data):
        """Deal with trade data, and publish trade message to EventCenter via TradeEvent."""
        symbol = data["instrument_id"]
        if symbol not in self._symbols:
            return
        action = ORDER_ACTION_BUY if data[
            "side"] == "buy" else ORDER_ACTION_SELL
        price = "%.8f" % float(data["price"])
        quantity = "%.8f" % float(data["qty"])
        timestamp = tools.utctime_str_to_mts(data["timestamp"])

        # Publish EventTrade.
        trade = {
            "platform": self._platform,
            "symbol": symbol,
            "action": action,
            "price": price,
            "quantity": quantity,
            "timestamp": timestamp
        }
        EventTrade(**trade).publish()
        logger.info("symbol:", symbol, "trade:", trade, caller=self)

    async def process_kline(self, data):
        """ Deal with 1min kline data, and publish kline message to EventCenter via KlineEvent.

        Args:
            data: Newest kline data.
        """
        symbol = data["instrument_id"]
        if symbol not in self._symbols:
            return
        timestamp = tools.utctime_str_to_mts(data["candle"][0])
        _open = "%.8f" % float(data["candle"][1])
        high = "%.8f" % float(data["candle"][2])
        low = "%.8f" % float(data["candle"][3])
        close = "%.8f" % float(data["candle"][4])
        volume = "%.8f" % float(data["candle"][5])

        # Publish EventKline
        kline = {
            "platform": self._platform,
            "symbol": symbol,
            "open": _open,
            "high": high,
            "low": low,
            "close": close,
            "volume": volume,
            "timestamp": timestamp,
            "kline_type": const.MARKET_TYPE_KLINE
        }
        EventKline(**kline).publish()
        logger.info("symbol:", symbol, "kline:", kline, caller=self)

    def _initialize(self):
        """ Initialize."""
        for channel in self._channels:
            if channel == const.MARKET_TYPE_KLINE_5M:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_5M)
            elif channel == const.MARKET_TYPE_KLINE_15M:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_15M)
            elif channel == const.MARKET_TYPE_KLINE_1H:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_1H)
            # else:
            #     logger.error("channel error! channel:", channel, caller=self)

    async def create_kline_tasks(self, kline_type, *args, **kwargs):
        """ Create some tasks to fetch kline information

        Args:
            kline_type: Type of line, kline or kline_5m or kline_15m

        NOTE: Because of REST API request limit, we only send one request pre minute.
        """
        for index, symbol in enumerate(self._symbols):
            asyncio.get_event_loop().call_later(index, self.delay_kline_update,
                                                symbol, kline_type)

    def delay_kline_update(self, symbol, kline_type):
        """ Do kline update.
        """
        asyncio.get_event_loop().create_task(
            self.do_kline_update(symbol, kline_type))

    async def do_kline_update(self, symbol, kline_type):
        if kline_type == const.MARKET_TYPE_KLINE_5M:
            range_type = 5 * 60
        elif kline_type == const.MARKET_TYPE_KLINE_15M:
            range_type = 15 * 60
        elif kline_type == const.MARKET_TYPE_KLINE_1H:
            range_type = 60 * 60
        else:
            return
        start = get_timestamp(datetime.datetime.now() -
                              datetime.timedelta(days=5))
        end = get_timestamp()
        result, error = await self._rest_api.get_kline(symbol, start, end,
                                                       range_type)

        if error:
            return

        open, hig, low, close = [], [], [], []
        if len(result):
            for item in result:
                open.append(item[1])
                hig.append(item[2])
                low.append(item[3])
                close.append(item[4])

        kline = {
            "platform": self._platform,
            "symbol": symbol,
            "open": open[::-1],  #最近的数据在最后面
            "high": hig[::-1],
            "low": low[::-1],
            "close": close[::-1],
            "kline_type": kline_type
        }
        print(kline)
        EventKline(**kline).publish()
        logger.info("symbol:", symbol, "kline:", kline, caller=self)
Exemplo n.º 19
0
class OKEx:
    """ OKEx Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `okex` or `okex_margin`.
            host: Exchange Websocket host address, default is `wss://real.okex.com:8443`.
            symbols: symbol list, OKEx Future instrument_id list.
            channels: channel list, only `orderbook`, `kline` and `trade` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """

    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://real.okex.com:8443")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._orderbooks = {}  # 订单薄数据 {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}

        url = self._wss + "/ws/v3"
        self._ws = Websocket(url, connected_callback=self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 5)

    async def connected_callback(self):
        """After create Websocket connection successfully, we will subscribing orderbook/trade/kline."""
        ches = []
        for ch in self._channels:
            if ch == "orderbook":
                for symbol in self._symbols:
                    ch = "spot/depth:{s}".format(s=symbol.replace("/", '-'))
                    ches.append(ch)
            elif ch == "trade":
                for symbol in self._symbols:
                    ch = "spot/trade:{s}".format(s=symbol.replace("/", '-'))
                    ches.append(ch)
            elif ch == "kline":
                for symbol in self._symbols:
                    ch = "spot/candle60s:{s}".format(s=symbol.replace("/", '-'))
                    ches.append(ch)
            else:
                logger.error("channel error! channel:", ch, caller=self)
        if ches:
            msg = {
                "op": "subscribe",
                "args": ches
            }
            await self._ws.send(msg)
            logger.info("subscribe orderbook/trade/kline success.", caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        data = "ping"
        if not self._ws:
            logger.error("Websocket connection not yeah!", caller=self)
            return
        await self._ws.send(data)

    async def process_binary(self, raw):
        """ Process binary message that received from Websocket connection.

        Args:
            raw: Raw message that received from Websocket connection.
        """
        decompress = zlib.decompressobj(-zlib.MAX_WBITS)
        msg = decompress.decompress(raw)
        msg += decompress.flush()
        msg = msg.decode()
        # logger.debug("msg:", msg, caller=self)
        if msg == "pong":
            return
        msg = json.loads(msg)

        table = msg.get("table")
        if table == "spot/depth":
            if msg.get("action") == "partial":
                for d in msg["data"]:
                    await self.process_orderbook_partial(d)
            elif msg.get("action") == "update":
                for d in msg["data"]:
                    await self.deal_orderbook_update(d)
            else:
                logger.warn("unhandle msg:", msg, caller=self)
        elif table == "spot/trade":
            for d in msg["data"]:
                await self.process_trade(d)
        elif table == "spot/candle60s":
            for d in msg["data"]:
                await self.process_kline(d)

    async def process_orderbook_partial(self, data):
        """Process orderbook partical data."""
        symbol = data.get("instrument_id").replace("-", "/")
        if symbol not in self._symbols:
            return
        asks = data.get("asks")
        bids = data.get("bids")
        self._orderbooks[symbol] = {"asks": {}, "bids": {}, "timestamp": 0}
        for ask in asks:
            price = float(ask[0])
            quantity = float(ask[1])
            self._orderbooks[symbol]["asks"][price] = quantity
        for bid in bids:
            price = float(bid[0])
            quantity = float(bid[1])
            self._orderbooks[symbol]["bids"][price] = quantity
        timestamp = tools.utctime_str_to_mts(data.get("timestamp"))
        self._orderbooks[symbol]["timestamp"] = timestamp

    async def deal_orderbook_update(self, data):
        """Process orderbook update data."""
        symbol = data.get("instrument_id").replace("-", "/")
        asks = data.get("asks")
        bids = data.get("bids")
        timestamp = tools.utctime_str_to_mts(data.get("timestamp"))

        if symbol not in self._orderbooks:
            return
        self._orderbooks[symbol]["timestamp"] = timestamp

        for ask in asks:
            price = float(ask[0])
            quantity = float(ask[1])
            if quantity == 0 and price in self._orderbooks[symbol]["asks"]:
                self._orderbooks[symbol]["asks"].pop(price)
            else:
                self._orderbooks[symbol]["asks"][price] = quantity

        for bid in bids:
            price = float(bid[0])
            quantity = float(bid[1])
            if quantity == 0 and price in self._orderbooks[symbol]["bids"]:
                self._orderbooks[symbol]["bids"].pop(price)
            else:
                self._orderbooks[symbol]["bids"][price] = quantity

        await self.publish_orderbook(symbol)

    async def publish_orderbook(self, symbol):
        """Publish OrderbookEvent."""
        ob = copy.copy(self._orderbooks[symbol])
        if not ob["asks"] or not ob["bids"]:
            logger.warn("symbol:", symbol, "asks:", ob["asks"], "bids:", ob["bids"], caller=self)
            return

        ask_keys = sorted(list(ob["asks"].keys()))
        bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:", symbol, "ask1:", ask_keys[0], "bid1:", bid_keys[0], caller=self)
            return

        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["asks"].get(k)
            asks.append([price, quantity])

        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["bids"].get(k)
            bids.append([price, quantity])

        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": ob["timestamp"]
        }
        EventOrderbook(**orderbook).publish()
        logger.debug("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_trade(self, data):
        """Process trade data and publish TradeEvent."""
        symbol = data.get("instrument_id").replace("-", "/")
        if symbol not in self._symbols:
            return
        action = ORDER_ACTION_BUY if data["side"] == "buy" else ORDER_ACTION_SELL
        price = "%.8f" % float(data["price"])
        quantity = "%.8f" % float(data["size"])
        timestamp = tools.utctime_str_to_mts(data["timestamp"])

        trade = {
            "platform": self._platform,
            "symbol": symbol,
            "action": action,
            "price": price,
            "quantity": quantity,
            "timestamp": timestamp
        }
        EventTrade(**trade).publish()
        logger.debug("symbol:", symbol, "trade:", trade, caller=self)

    async def process_kline(self, data):
        """Process kline data and publish KlineEvent."""
        symbol = data["instrument_id"].replace("-", "/")
        if symbol not in self._symbols:
            return
        timestamp = tools.utctime_str_to_mts(data["candle"][0])
        _open = "%.8f" % float(data["candle"][1])
        high = "%.8f" % float(data["candle"][2])
        low = "%.8f" % float(data["candle"][3])
        close = "%.8f" % float(data["candle"][4])
        volume = "%.8f" % float(data["candle"][5])

        kline = {
            "platform": self._platform,
            "symbol": symbol,
            "open": _open,
            "high": high,
            "low": low,
            "close": close,
            "volume": volume,
            "timestamp": timestamp,
            "kline_type": const.MARKET_TYPE_KLINE
        }
        EventKline(**kline).publish()
        logger.debug("symbol:", symbol, "kline:", kline, caller=self)
Exemplo n.º 20
0
class BinanceTrade:
    """ Binance Trade module. You can initialize trade object with some attributes in kwargs.

    Attributes:
        account: Account name for this trade exchange.
        strategy: What's name would you want to created for you strategy.
        symbol: Symbol name for your trade.
        host: HTTP request host. (default "https://api.binance.com")
        wss: Websocket address. (default "wss://stream.binance.com:9443")
        access_key: Account's ACCESS KEY.
        secret_key Account's SECRET KEY.
        asset_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `asset_update_callback` is like `async def on_asset_update_callback(asset: Asset): pass` and this
            callback function will be executed asynchronous when received AssetEvent.
        order_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `order_update_callback` is like `async def on_order_update_callback(order: Order): pass` and this
            callback function will be executed asynchronous when some order state updated.
        init_success_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `init_success_callback` is like `async def on_init_success_callback(success: bool, error: Error, **kwargs): pass`
            and this callback function will be executed asynchronous after Trade module object initialized successfully.
    """
    def __init__(self, **kwargs):
        """Initialize Trade module."""
        e = None
        if not kwargs.get("account"):
            e = Error("param account miss")
        if not kwargs.get("strategy"):
            e = Error("param strategy miss")
        if not kwargs.get("symbol"):
            e = Error("param symbol miss")
        if not kwargs.get("host"):
            kwargs["host"] = "https://api.binance.com"
        if not kwargs.get("wss"):
            kwargs["wss"] = "wss://stream.binance.com:9443"
        if not kwargs.get("access_key"):
            e = Error("param access_key miss")
        if not kwargs.get("secret_key"):
            e = Error("param secret_key miss")
        if e:
            logger.error(e, caller=self)
            SingleTask.run(kwargs["init_success_callback"], False, e)
            return

        self._account = kwargs["account"]
        self._strategy = kwargs["strategy"]
        self._platform = BINANCE
        self._symbol = kwargs["symbol"]
        self._host = kwargs["host"]
        self._wss = kwargs["wss"]
        self._access_key = kwargs["access_key"]
        self._secret_key = kwargs["secret_key"]
        self._asset_update_callback = kwargs.get("asset_update_callback")
        self._order_update_callback = kwargs.get("order_update_callback")
        self._init_success_callback = kwargs.get("init_success_callback")

        super(BinanceTrade, self).__init__(self._wss)

        self._raw_symbol = self._symbol.replace(
            "/", "")  # Row symbol name, same as Binance Exchange.

        self._listen_key = None  # Listen key for Websocket authentication.
        self._assets = {
        }  # Asset data. e.g. {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }
        self._orders = {}  # Order data. e.g. {order_no: order, ... }

        # Initialize our REST API client.
        self._rest_api = BinanceRestAPI(self._host, self._access_key,
                                        self._secret_key)

        # Subscribe our AssetEvent.
        if self._asset_update_callback:
            AssetSubscribe(self._platform, self._account,
                           self.on_event_asset_update)

        # Create a loop run task to reset listen key every 30 minutes.
        LoopRunTask.register(self._reset_listen_key, 60 * 30)

        # Create a coroutine to initialize Websocket connection.
        SingleTask.run(self._init_websocket)

    @property
    def assets(self):
        return copy.copy(self._assets)

    @property
    def orders(self):
        return copy.copy(self._orders)

    @property
    def rest_api(self):
        return self._rest_api

    async def _init_websocket(self):
        """ Initialize Websocket connection.
        """
        # Get listen key first.
        success, error = await self._rest_api.get_listen_key()
        if error:
            e = Error("get listen key failed: {}".format(error))
            logger.error(e, caller=self)
            SingleTask.run(self._init_success_callback, False, e)
            return
        self._listen_key = success["listenKey"]
        uri = "/ws/" + self._listen_key
        url = urljoin(self._wss, uri)
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

    async def _reset_listen_key(self, *args, **kwargs):
        """ Reset listen key.
        """
        if not self._listen_key:
            logger.error("listen key not initialized!", caller=self)
            return
        await self._rest_api.put_listen_key(self._listen_key)
        logger.info("reset listen key success!", caller=self)

    async def connected_callback(self):
        """ After websocket connection created successfully, pull back all open order information.
        """
        logger.info("Websocket connection authorized successfully.",
                    caller=self)
        order_infos, error = await self._rest_api.get_open_orders(
            self._raw_symbol)
        if error:
            e = Error("get open orders error: {}".format(error))
            SingleTask.run(self._init_success_callback, False, e)
            return
        for order_info in order_infos:
            order_no = "{}_{}".format(order_info["orderId"],
                                      order_info["clientOrderId"])
            if order_info["status"] == "NEW":
                status = ORDER_STATUS_SUBMITTED
            elif order_info["status"] == "PARTIALLY_FILLED":
                status = ORDER_STATUS_PARTIAL_FILLED
            elif order_info["status"] == "FILLED":
                status = ORDER_STATUS_FILLED
            elif order_info["status"] == "CANCELED":
                status = ORDER_STATUS_CANCELED
            elif order_info["status"] == "REJECTED":
                status = ORDER_STATUS_FAILED
            elif order_info["status"] == "EXPIRED":
                status = ORDER_STATUS_FAILED
            else:
                logger.warn("unknown status:", order_info, caller=self)
                continue

            info = {
                "platform":
                self._platform,
                "account":
                self._account,
                "strategy":
                self._strategy,
                "order_no":
                order_no,
                "action":
                order_info["side"],
                "order_type":
                order_info["type"],
                "symbol":
                self._symbol,
                "price":
                order_info["price"],
                "quantity":
                order_info["origQty"],
                "remain":
                float(order_info["origQty"]) -
                float(order_info["executedQty"]),
                "status":
                status,
                "ctime":
                order_info["time"],
                "utime":
                order_info["updateTime"]
            }
            order = Order(**info)
            self._orders[order_no] = order
            SingleTask.run(self._order_update_callback, copy.copy(order))

        SingleTask.run(self._init_success_callback, True, None)

    async def create_order(self, action, price, quantity, *args, **kwargs):
        """ Create an order.

        Args:
            action: Trade direction, BUY or SELL.
            price: Price of each contract.
            quantity: The buying or selling quantity.

        Returns:
            order_no: Order ID if created successfully, otherwise it's None.
            error: Error information, otherwise it's None.
        """
        price = tools.float_to_str(price)
        quantity = tools.float_to_str(quantity)
        client_order_id = kwargs.get("client_order_id")
        result, error = await self._rest_api.create_order(
            action,
            self._raw_symbol,
            price,
            quantity,
            client_order_id=client_order_id)
        if error:
            return None, error
        order_no = "{}_{}".format(result["orderId"], result["clientOrderId"])
        return order_no, None

    async def revoke_order(self, *order_nos):
        """ Revoke (an) order(s).

        Args:
            order_nos: Order id list, you can set this param to 0 or multiple items. If you set 0 param, you can cancel
                all orders for this symbol(initialized in Trade object). If you set 1 param, you can cancel an order.
                If you set multiple param, you can cancel multiple orders. Do not set param length more than 100.

        Returns:
            Success or error, see bellow.
        """
        # If len(order_nos) == 0, you will cancel all orders for this symbol(initialized in Trade object).
        if len(order_nos) == 0:
            order_infos, error = await self._rest_api.get_open_orders(
                self._raw_symbol)
            if error:
                return False, error
            for order_info in order_infos:
                _, error = await self._rest_api.revoke_order(
                    self._raw_symbol, order_info["orderId"],
                    order_info["clientOrderId"])
                if error:
                    return False, error
            return True, None

        # If len(order_nos) == 1, you will cancel an order.
        if len(order_nos) == 1:
            order_id, client_order_id = order_nos[0].split("_")
            success, error = await self._rest_api.revoke_order(
                self._raw_symbol, order_id, client_order_id)
            if error:
                return order_nos[0], error
            else:
                return order_nos[0], None

        # If len(order_nos) > 1, you will cancel multiple orders.
        if len(order_nos) > 1:
            success, error = [], []
            for order_no in order_nos:
                order_id, client_order_id = order_no.split("_")
                _, e = await self._rest_api.revoke_order(
                    self._raw_symbol, order_id, client_order_id)
                if e:
                    error.append((order_no, e))
                else:
                    success.append(order_no)
            return success, error

    async def get_open_order_nos(self):
        """ Get open order no list.
        """
        success, error = await self._rest_api.get_open_orders(self._raw_symbol)
        if error:
            return None, error
        else:
            order_nos = []
            for order_info in success:
                order_no = "{}_{}".format(order_info["orderId"],
                                          order_info["clientOrderId"])
                order_nos.append(order_no)
            return order_nos, None

    @async_method_locker("BinanceTrade.process.locker")
    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: message received from Websocket connection.
        """
        logger.debug("msg:", json.dumps(msg), caller=self)
        e = msg.get("e")
        if e == "executionReport":  # Order update.
            if msg["s"] != self._raw_symbol:
                return
            order_no = "{}_{}".format(msg["i"], msg["c"])
            if msg["X"] == "NEW":
                status = ORDER_STATUS_SUBMITTED
            elif msg["X"] == "PARTIALLY_FILLED":
                status = ORDER_STATUS_PARTIAL_FILLED
            elif msg["X"] == "FILLED":
                status = ORDER_STATUS_FILLED
            elif msg["X"] == "CANCELED":
                status = ORDER_STATUS_CANCELED
            elif msg["X"] == "REJECTED":
                status = ORDER_STATUS_FAILED
            elif msg["X"] == "EXPIRED":
                status = ORDER_STATUS_FAILED
            else:
                logger.warn("unknown status:", msg, caller=self)
                return
            order = self._orders.get(order_no)
            if not order:
                info = {
                    "platform": self._platform,
                    "account": self._account,
                    "strategy": self._strategy,
                    "order_no": order_no,
                    "client_order_id": msg["c"],
                    "action": msg["S"],
                    "order_type": msg["o"],
                    "symbol": self._symbol,
                    "price": msg["p"],
                    "quantity": msg["q"],
                    "ctime": msg["O"]
                }
                order = Order(**info)
                self._orders[order_no] = order
            order.remain = float(msg["q"]) - float(msg["z"])
            order.status = status
            order.utime = msg["T"]
            SingleTask.run(self._order_update_callback, copy.copy(order))

    async def on_event_asset_update(self, asset: Asset):
        """ Asset data update callback.

        Args:
            asset: Asset object.
        """
        self._assets = asset
        SingleTask.run(self._asset_update_callback, asset)
Exemplo n.º 21
0
class HuobiFutureMarket:
    """ Huobi Future Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `huobi_future`.
            wss: Exchange Websocket host address, default is `wss://www.hbdm.com`.
            symbols: symbol list, Huobi Future contract code list.
            channels: channel list, only `orderbook` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.hbdm.com")
        self._host = kwargs.get("host", "https://api.hbdm.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = kwargs.get("access_key", "")
        self._secret_key = kwargs.get("secret_key", "")

        self._c_to_s = {}  # {"channel": "symbol"}

        url = self._wss + "/ws"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

        self._rest_api = HuobiFutureRestAPI(self._host, self._access_key,
                                            self._secret_key)
        self._initialize()

    async def connected_callback(self):
        """ After create connection to Websocket server successfully, we will subscribe orderbook event.
        """
        for ch in self._channels:
            if ch == "kline":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "kline")
                    if not channel:
                        continue
                    kline = {"sub": channel}
                    await self._ws.send(kline)
            elif ch == "orderbook":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "depth")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)
            elif ch == "trade":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "trade")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)

    async def process_binary(self, msg):
        """ Process binary message that received from Websocket connection.

        Args:
            msg: Binary message.
        """
        data = json.loads(gzip.decompress(msg).decode())
        # logger.debug("data:", json.dumps(data), caller=self)
        channel = data.get("ch")
        if not channel:
            if data.get("ping"):
                hb_msg = {"pong": data.get("ping")}
                await self._ws.send(hb_msg)
            return

        symbol = self._c_to_s[channel]

        if channel.find("kline") != -1:
            d = data.get("tick")
            kline = {
                "platform": self._platform,
                "symbol": symbol,
                "open": d["open"],
                "high": d["high"],
                "low": d["low"],
                "close": d["close"],
                "volume": d["amount"],
                "timestamp": data.get("ts"),
                "kline_type": MARKET_TYPE_KLINE
            }
            EventKline(**kline).publish()
            logger.debug("symbol:", symbol, "kline:", kline, caller=self)
        elif channel.find("depth") != -1:
            tick = data.get("tick")
            asks = tick.get("asks")[:self._orderbook_length]
            bids = tick.get("bids")[:self._orderbook_length]
            timestamp = tick.get("ts")
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": asks,
                "bids": bids,
                "timestamp": timestamp
            }
            EventOrderbook(**orderbook).publish()
            logger.debug("symbol:",
                         symbol,
                         "orderbook:",
                         orderbook,
                         caller=self)
        elif channel.find("trade") != -1:
            tick = data.get("tick")
            direction = tick["data"][0].get("direction")
            price = tick["data"][0].get("price")
            quantity = tick["data"][0].get("amount")
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action":
                ORDER_ACTION_BUY if direction == "buy" else ORDER_ACTION_SELL,
                "price": price,
                "quantity": quantity,
                "timestamp": tick.get("ts")
            }
            EventTrade(**trade).publish()
            logger.debug("symbol:", symbol, "trade:", trade, caller=self)
        else:
            logger.error("event error! msg:", msg, caller=self)

    def _symbol_to_channel(self, symbol, channel_type):
        if channel_type == "kline":
            channel = "market.{s}.kline.1min".format(s=symbol)
        elif channel_type == "depth":
            channel = "market.{s}.depth.step6".format(s=symbol)
        elif channel_type == "trade":
            channel = "market.{s}.trade.detail".format(s=symbol)
        else:
            logger.error("channel type error! channel type:",
                         channel_type,
                         calle=self)
            return None
        self._c_to_s[channel] = symbol
        return channel

    def _initialize(self):
        """ Initialize."""
        for channel in self._channels:
            if channel == const.MARKET_TYPE_KLINE_5M:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_5M)
            elif channel == const.MARKET_TYPE_KLINE_15M:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_15M)
            elif channel == const.MARKET_TYPE_KLINE_1H:
                heartbeat.register(self.create_kline_tasks, 1,
                                   const.MARKET_TYPE_KLINE_1H)
            # else:
            #     logger.error("channel error! channel:", channel, caller=self)

    async def create_kline_tasks(self, kline_type, *args, **kwargs):
        """ Create some tasks to fetch kline information

        Args:
            kline_type: Type of line, kline or kline_5m or kline_15m

        NOTE: Because of REST API request limit, we only send one request pre minute.
        """
        for index, symbol in enumerate(self._symbols):
            asyncio.get_event_loop().call_later(index, self.delay_kline_update,
                                                symbol, kline_type)

    def delay_kline_update(self, symbol, kline_type):
        """ Do kline update.
        """
        asyncio.get_event_loop().create_task(
            self.do_kline_update(symbol, kline_type))

    async def do_kline_update(self, symbol, kline_type):
        if kline_type == const.MARKET_TYPE_KLINE_5M:
            range_type = "5min"
        elif kline_type == const.MARKET_TYPE_KLINE_15M:
            range_type = "15min"
        elif kline_type == const.MARKET_TYPE_KLINE_1H:
            range_type = "60min"
        elif kline_type == const.MARKET_TYPE_KLINE_1H:
            range_type = "4hour"
        elif kline_type == const.MARKET_TYPE_KLINE_1H:
            range_type = "1day"
        else:
            return
        result, error = await self._rest_api.get_kline(symbol,
                                                       size=130,
                                                       period=range_type)

        if error:
            return

        open, hig, low, close = [], [], [], []
        if len(result):
            for item in result["data"]:
                open.append(item["open"])
                hig.append(item["high"])
                low.append(item["low"])
                close.append(item["close"])

        kline = {
            "platform": self._platform,
            "symbol": symbol,
            "open": open,  #最近的数据在最后面
            "high": hig,
            "low": low,
            "close": close,
            "kline_type": kline_type
        }
        EventKline(**kline).publish()
        logger.info("symbol:",
                    symbol,
                    "kline_%s:" % range_type,
                    kline,
                    caller=self)
Exemplo n.º 22
0
class CoinbaseMarket:
    """ Coinbase Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `coinbase`.
            wss: Exchange Websocket host address, default is "wss://ws-feed.pro.coinbase.com".
            symbols: Trade pair list, e.g. ["ETH/USD"].
            channels: channel list, only `orderbook` and `trade` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://ws-feed.pro.coinbase.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._orderbooks = {
        }  # orderbook datas {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}}}
        self._symbols_map = {
        }  # config symbol name to raw symbol name, e.g. {"BTC-USD": "BTC/USD"}

        url = self._wss
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

    async def connected_callback(self):
        """ After create Websocket connection successfully, we will subscribing orderbook/trade events.
        """
        symbols = []
        for s in self._symbols:
            t = s.replace("/", "-")
            symbols.append(t)
            self._symbols_map[t] = s

        if not symbols:
            logger.warn("symbols not found in config file.", caller=self)
            return
        if not self._channels:
            logger.warn("channels not found in config file.", caller=self)
            return

        channels = []
        for ch in self._channels:
            if ch == "orderbook":
                sub = {"name": "level2", "product_ids": symbols}
                channels.append(sub)
            elif ch == "trade":
                sub = {"name": "ticker", "product_ids": symbols}
                channels.append(sub)
            else:
                logger.error("channel error! channel:", ch, caller=self)
        if channels:
            msg = {"type": "subscribe", "channels": channels}
            await self._ws.send(msg)
            logger.info("subscribe orderbook/trade success.", caller=self)

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message data that received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)

        t = msg.get("type")
        if t == "snapshot":
            await self.process_orderbook_snapshot(msg)
        elif t == "l2update":
            await self.process_orderbook_update(msg)
        elif t == "ticker":
            await self.process_trade(msg)

    @async_method_locker("process_orderbook_update")
    async def process_orderbook_snapshot(self, msg):
        """ Deal with orderbook snapshot message.

        Args:
            msg: Orderbook snapshot message.
        """
        symbol = msg["product_id"].replace("-", "/")
        if symbol not in self._symbols:
            return
        asks = msg.get("asks")
        bids = msg.get("bids")
        self._orderbooks[symbol] = {"asks": {}, "bids": {}, "timestamp": 0}
        for ask in asks[:200]:
            price = float(ask[0])
            quantity = float(ask[1])
            self._orderbooks[symbol]["asks"][price] = quantity
        for bid in bids[:200]:
            price = float(bid[0])
            quantity = float(bid[1])
            self._orderbooks[symbol]["bids"][price] = quantity

    @async_method_locker("process_orderbook_update")
    async def process_orderbook_update(self, msg):
        """ Deal with orderbook message that updated.

        Args:
            msg: Orderbook update message.
        """
        symbol = msg["product_id"].replace("-", "/")
        if symbol not in self._symbols:
            return

        self._orderbooks[symbol]["timestamp"] = tools.utctime_str_to_mts(
            msg["time"][:23] + "Z")
        for item in msg["changes"]:
            side = item[0]
            price = float(item[1])
            quantity = float(item[2])
            if side == "sell":
                if quantity == 0:
                    if price in self._orderbooks[symbol]["asks"]:
                        self._orderbooks[symbol]["asks"].pop(price)
                else:
                    self._orderbooks[symbol]["asks"][price] = quantity
            elif side == "buy":
                if quantity == 0:
                    if price in self._orderbooks[symbol]["bids"]:
                        self._orderbooks[symbol]["bids"].pop(price)
                else:
                    self._orderbooks[symbol]["bids"][price] = quantity

        await self.publish_orderbook_event(symbol)

    async def publish_orderbook_event(self, symbol):
        """Publish OrderbookEvent."""
        ob = copy.copy(self._orderbooks[symbol])
        if not ob["asks"] or not ob["bids"]:
            logger.warn("symbol:",
                        symbol,
                        "asks:",
                        ob["asks"],
                        "bids:",
                        ob["bids"],
                        caller=self)
            return

        ask_keys = sorted(list(ob["asks"].keys()))
        bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:",
                        symbol,
                        "ask1:",
                        ask_keys[0],
                        "bid1:",
                        bid_keys[0],
                        caller=self)
            return

        # asks
        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["asks"].get(k)
            asks.append([price, quantity])

        # bids
        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = "%.8f" % ob["bids"].get(k)
            bids.append([price, quantity])

        # Publish OrderbookEvent.
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": tools.get_cur_timestamp_ms()
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_trade(self, msg):
        """Process trade data and publish TradeEvent."""
        symbol = msg["product_id"].replace("-", "/")
        if symbol not in self._symbols:
            return
        if not msg.get(
                "trade_id"
        ):  # The first ticker message received from Websocket maybe no this field, dropped.
            return
        action = ORDER_ACTION_BUY if msg.get(
            "side") == "buy" else ORDER_ACTION_SELL
        price = "%.8f" % float(msg["price"])
        quantity = "%.8f" % float(msg["last_size"])
        timestamp = tools.utctime_str_to_mts(msg["time"][:23] + "Z")

        trade = {
            "platform": self._platform,
            "symbol": symbol,
            "action": action,
            "price": price,
            "quantity": quantity,
            "timestamp": timestamp
        }
        EventTrade(**trade).publish()
        logger.info("symbol:", symbol, "trade:", trade, caller=self)
Exemplo n.º 23
0
class KrakenMarket:
    """ Kraken Market Server

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `kraken`.
            host: HTTP host, default is `https://api.kraken.com`.
            wss: Wss host, default is `wss://ws.kraken.com`.
            symbols: Symbol name list, e.g. XTB/USD. (Trade pair name list)
            channels: What are channels to be subscribed, only support `orderbook` and `trade`.
            orderbook_interval: The interval time to fetch a orderbook information, default is 2 seconds.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        """Initialize."""
        self._platform = kwargs["platform"]
        self._host = kwargs.get("host", "https://api.kraken.com")
        self._wss = kwargs.get("wss", "wss://ws.kraken.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_interval = kwargs.get("orderbook_interval", 2)
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self.heartbeat_msg = {}
        self._orderbooks = {
        }  # orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}, "timestamp": 0}, ... }

        self._rest_api = KrakenRestAPI(self._host, None, None)

        url = self._wss
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

        # Register a loop run task to reset heartbeat message request id.
        LoopRunTask.register(self.send_heartbeat_msg, 5)

    async def connected_callback(self):
        """After create Websocket connection successfully, we will subscribing orderbook/trade/kline."""
        if not self._symbols:
            logger.warn("symbols not found in config file.", caller=self)
            return
        if not self._channels:
            logger.warn("channels not found in config file.", caller=self)
            return
        for ch in self._channels:
            if ch == "orderbook":
                # subscription = {"name": "book"}
                LoopRunTask.register(self.on_event_update_orderbook,
                                     self._orderbook_interval)
                continue
            elif ch == "trade":
                subscription = {"name": "trade"}
            # elif ch == "kline":  # TODO: something wrong from exchange server? subscribe ohlc-1 but receive ohlc-5 ?
            #     subscription = {"name": "ohlc", "interval": 1}
            else:
                logger.error("channel error:", ch, caller=self)
                continue

            d = {
                "event": "subscribe",
                "pair": self._symbols,
                "subscription": subscription
            }
            await self._ws.send(d)
            logger.info("subscribe", ch, "success.", caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        data = {"event": "ping", "reqid": tools.get_cur_timestamp()}
        if not self._ws:
            logger.error("Websocket connection not yeah!", caller=self)
            return
        await self._ws.send(data)

    async def process(self, msg):
        """ Process message that received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)

        if not isinstance(msg, list):
            return

        if msg[-2] == "book-10":
            await self.deal_orderbook_update(msg)
        elif msg[-2] == "trade":
            await self.deal_trade_update(msg)

    @async_method_locker("deal_orderbook_update")
    async def deal_orderbook_update(self, msg):
        """ Deal with orderbook message that updated.
        """
        data = msg[1]
        symbol = msg[-1]
        if symbol not in self._symbols:
            return
        if symbol not in self._orderbooks:
            self._orderbooks[symbol] = {"asks": {}, "bids": {}, "timestamp": 0}
            a = data["as"]
            b = data["bs"]
        else:
            a = data.get("a", [])
            b = data.get("b", [])
        for item in a:
            price = float(item[0])
            quantity = float(item[1])
            if quantity == 0:
                if price in self._orderbooks[symbol]["asks"]:
                    self._orderbooks[symbol]["asks"].pop(price)
            else:
                self._orderbooks[symbol]["asks"][price] = quantity
                self._orderbooks[symbol]["timestamp"] = int(
                    float(item[2]) * 1000)
        for item in b:
            price = float(item[0])
            quantity = float(item[1])
            if quantity == 0:
                if price in self._orderbooks[symbol]["bids"]:
                    self._orderbooks[symbol]["bids"].pop(price)
            else:
                self._orderbooks[symbol]["bids"][price] = quantity
                self._orderbooks[symbol]["timestamp"] = int(
                    float(item[2]) * 1000)

        await self.publish_orderbook()

    async def publish_orderbook(self, *args, **kwargs):
        """ Publish OrderbookEvent.
        """
        for symbol, data in self._orderbooks.items():
            ob = copy.copy(data)
            if not ob["asks"] or not ob["bids"]:
                logger.warn("symbol:",
                            symbol,
                            "asks:",
                            ob["asks"],
                            "bids:",
                            ob["bids"],
                            caller=self)
                continue

            ask_keys = sorted(list(ob["asks"].keys()))
            bid_keys = sorted(list(ob["bids"].keys()), reverse=True)
            if ask_keys[0] <= bid_keys[0]:
                logger.warn("symbol:",
                            symbol,
                            "ask1:",
                            ask_keys[0],
                            "bid1:",
                            bid_keys[0],
                            caller=self)
                continue

            # asks
            asks = []
            for k in ask_keys[:self._orderbook_length]:
                price = "%.8f" % k
                quantity = "%.8f" % ob["asks"].get(k)
                asks.append([price, quantity])

            # bids
            bids = []
            for k in bid_keys[:self._orderbook_length]:
                price = "%.8f" % k
                quantity = "%.8f" % ob["bids"].get(k)
                bids.append([price, quantity])

            # Publish OrderbookEvent.
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": asks,
                "bids": bids,
                "timestamp": ob["timestamp"]
            }
            EventOrderbook(**orderbook).publish()
            logger.info("symbol:",
                        symbol,
                        "orderbook:",
                        orderbook,
                        caller=self)

    async def deal_trade_update(self, msg):
        """ Deal with trade message that updated.
        """
        data = msg[1]
        symbol = msg[-1]
        if symbol not in self._symbols:
            return

        for item in data:
            price = "%.8f" % float(item[0])
            quantity = "%.8f" % float(item[1])
            timestamp = int(float(item[2]) * 1000)
            action = ORDER_ACTION_BUY if item[3] == "b" else ORDER_ACTION_SELL

            # Publish TradeEvent.
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action": action,
                "price": price,
                "quantity": quantity,
                "timestamp": timestamp
            }
            EventTrade(**trade).publish()
            logger.info("symbol:", symbol, "trade:", trade, caller=self)

    async def on_event_update_orderbook(self, *args, **kwargs):
        """ Loop run to fetch orderbook information.
        """
        for symbol in self._symbols:
            SingleTask.run(self.get_newest_orderbook, symbol)

    async def get_newest_orderbook(self, symbol):
        """ Get the newest orderbook information.
        """
        result, error = await self._rest_api.get_orderbook(
            symbol.replace("/", ""), self._orderbook_length)
        key = list(result.keys())[0]

        asks, bids = [], []
        for item in result.get(key)["asks"]:
            asks.append(item[:2])
        for item in result.get(key)["bids"]:
            bids.append(item[:2])
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": tools.get_cur_timestamp_ms()
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)
Exemplo n.º 24
0
class HuobiFutureMarket:
    """ Huobi Future Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `huobi_future`.
            wss: Exchange Websocket host address, default is `wss://www.hbdm.com`.
            symbols: symbol list, Huobi Future contract code list.
            channels: channel list, only `orderbook` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.hbdm.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._c_to_s = {}  # {"channel": "symbol"}

        url = self._wss + "/ws"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

    async def connected_callback(self):
        """ After create connection to Websocket server successfully, we will subscribe orderbook event.
        """
        for ch in self._channels:
            if ch == "kline":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "kline")
                    if not channel:
                        continue
                    kline = {"sub": channel}
                    await self._ws.send(kline)
            elif ch == "orderbook":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "depth")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)
            elif ch == "trade":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "trade")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)
            else:
                logger.error("channel error! channel:", ch, caller=self)

    async def process_binary(self, msg):
        """ Process binary message that received from Websocket connection.

        Args:
            msg: Binary message.
        """
        data = json.loads(gzip.decompress(msg).decode())
        # logger.debug("data:", json.dumps(data), caller=self)
        channel = data.get("ch")
        if not channel:
            if data.get("ping"):
                hb_msg = {"pong": data.get("ping")}
                await self._ws.send(hb_msg)
            return

        symbol = self._c_to_s[channel]

        if channel.find("kline") != -1:
            d = data.get("tick")
            kline = {
                "platform": self._platform,
                "symbol": symbol,
                "open": d["open"],
                "high": d["high"],
                "low": d["low"],
                "close": d["close"],
                "volume": d["amount"],
                "timestamp": data.get("ts"),
                "kline_type": MARKET_TYPE_KLINE
            }
            EventKline(**kline).publish()
            logger.debug("symbol:", symbol, "kline:", kline, caller=self)
        elif channel.find("depth") != -1:
            tick = data.get("tick")
            asks = tick.get("asks")[:self._orderbook_length]
            bids = tick.get("bids")[:self._orderbook_length]
            timestamp = tick.get("ts")
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": asks,
                "bids": bids,
                "timestamp": timestamp
            }
            EventOrderbook(**orderbook).publish()
            logger.debug("symbol:",
                         symbol,
                         "orderbook:",
                         orderbook,
                         caller=self)
        elif channel.find("trade") != -1:
            tick = data.get("tick")
            direction = tick["data"][0].get("direction")
            price = tick["data"][0].get("price")
            quantity = tick["data"][0].get("amount")
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action":
                ORDER_ACTION_BUY if direction == "buy" else ORDER_ACTION_SELL,
                "price": price,
                "quantity": quantity,
                "timestamp": tick.get("ts")
            }
            EventTrade(**trade).publish()
            logger.debug("symbol:", symbol, "trade:", trade, caller=self)
        else:
            logger.error("event error! msg:", msg, caller=self)

    def _symbol_to_channel(self, symbol, channel_type):
        if channel_type == "kline":
            channel = "market.{s}.kline.1min".format(s=symbol)
        elif channel_type == "depth":
            channel = "market.{s}.depth.step6".format(s=symbol)
        elif channel_type == "trade":
            channel = "market.{s}.trade.detail".format(s=symbol)
        else:
            logger.error("channel type error! channel type:",
                         channel_type,
                         calle=self)
            return None
        self._c_to_s[channel] = symbol
        return channel
Exemplo n.º 25
0
class KucoinMarket:
    """ Kucoin Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `kucoin`.
            host: Exchange Websocket host address, default is "https://openapi-v2.kucoin.com".
            symbols: symbol list, OKEx Future instrument_id list.
            channels: channel list, only `orderbook` , `kline` and `trade` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 20.
            orderbook_interval: The interval time to fetch a orderbook information, default is 2 seconds.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._host = kwargs.get("host", "https://openapi-v2.kucoin.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length",
                                            20)  # only support for 20 or 100.
        self._orderbook_interval = kwargs.get("orderbook_interval", 2)

        if self._orderbook_length != 20:
            self._orderbook_length = 100

        self._request_id = 0  # Unique request id for pre request.
        self._orderbooks = {
        }  # Orderbook data, e.g. {"symbol": {"bids": {"price": quantity, ...}, "asks": {...}, timestamp: 123, "sequence": 123}}
        self._last_publish_ts = 0  # The latest publish timestamp for OrderbookEvent.
        self._ws = None  # Websocket object.

        # REST API client.
        self._rest_api = KucoinRestAPI(self._host, None, None, None)

        SingleTask.run(self._initialize)

    async def _initialize(self):
        """Initialize."""
        for channel in self._channels:
            if channel == "orderbook":
                LoopRunTask.register(self.do_orderbook_update,
                                     self._orderbook_interval)
            elif channel == "trade":
                # Create Websocket connection.
                success, error = await self._rest_api.get_websocket_token()
                if error:
                    logger.error("get websocket token error!", caller=self)
                    return
                url = "{}?token={}".format(
                    success["instanceServers"][0]["endpoint"],
                    success["token"])
                self._ws = Websocket(url, self.connected_callback,
                                     self.process)
                self._ws.initialize()
                LoopRunTask.register(self.send_heartbeat_msg, 30)

    async def connected_callback(self):
        """ After create connection to Websocket server successfully, we will subscribe orderbook/kline/trade event.
        """
        symbols = []
        for s in self._symbols:
            t = s.replace("/", "-")
            symbols.append(t)
        if not symbols:
            logger.warn("symbols not found in config file.", caller=self)
            return
        if not self._channels:
            logger.warn("channels not found in config file.", caller=self)
            return
        for ch in self._channels:
            request_id = await self.generate_request_id()
            # if ch == "orderbook":
            #     d = {
            #         "id": request_id,
            #         "type": "subscribe",
            #         "topic": "/market/level2:" + ",".join(symbols),
            #         "response": True
            #     }
            #     await self._ws.send(d)
            #     logger.info("subscribe orderbook success.", caller=self)
            if ch == "trade":
                d = {
                    "id": request_id,
                    "type": "subscribe",
                    "topic": "/market/match:" + ",".join(symbols),
                    "privateChannel": False,
                    "response": True
                }
                await self._ws.send(d)
                logger.info("subscribe trade success.", caller=self)
            else:
                logger.error("channel error! channel:", ch, caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        request_id = await self.generate_request_id()
        data = {"id": request_id, "type": "ping"}
        if not self._ws:
            logger.error("")
            return
        await self._ws.send(data)

    @async_method_locker("_generate_request_id")
    async def generate_request_id(self):
        self._request_id = tools.get_cur_timestamp_ms()
        return self._request_id

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)

        topic = msg.get("topic", "")
        if "level2" in topic:
            await self.process_orderbook_update(msg["data"])
        elif "match" in topic:
            await self.process_trade_update(msg["data"])

    @async_method_locker("process")
    async def process_orderbook_update(self, data):
        """ Deal with orderbook update message.
        """
        symbol = data["symbol"].replace("-", "/")
        if symbol not in self._orderbooks:
            return

        if self._orderbooks[symbol]["sequence"] > data["sequenceStart"]:
            return

        self._orderbooks[symbol]["timestamp"] = tools.get_cur_timestamp_ms()

        for ask in data["changes"]["asks"]:
            price = float(ask[0])
            quantity = float(ask[1])
            if price == 0:
                continue
            if quantity == 0 and price in self._orderbooks[symbol]["asks"]:
                self._orderbooks[symbol]["asks"].pop(price)
            else:
                self._orderbooks[symbol]["asks"][price] = quantity

        for bid in data["changes"]["bids"]:
            price = float(bid[0])
            quantity = float(bid[1])
            if price == 0:
                continue
            if quantity == 0 and price in self._orderbooks[symbol]["bids"]:
                self._orderbooks[symbol]["bids"].pop(price)
            else:
                self._orderbooks[symbol]["bids"][price] = quantity

        if self._orderbooks[symbol][
                "timestamp"] - self._last_publish_ts > 1000:  # Only publish one per minute.
            self._last_publish_ts = self._orderbooks[symbol]["timestamp"]
            ob = copy.copy(self._orderbooks[symbol])
            SingleTask.run(self.publish_orderbook, symbol, ob)

    async def publish_orderbook(self, symbol, data):
        """ Publish orderbook message to EventCenter via OrderbookEvent.
        """
        if not data["asks"] or not data["bids"]:
            logger.warn("symbol:",
                        symbol,
                        "asks:",
                        data["asks"],
                        "bids:",
                        data["bids"],
                        caller=self)
            return

        ask_keys = sorted(list(data["asks"].keys()))
        bid_keys = sorted(list(data["bids"].keys()), reverse=True)
        if ask_keys[0] <= bid_keys[0]:
            logger.warn("symbol:",
                        symbol,
                        "ask1:",
                        ask_keys[0],
                        "bid1:",
                        bid_keys[0],
                        caller=self)
            return

        asks = []
        for k in ask_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = str(data["asks"].get(k))
            asks.append([price, quantity])

        bids = []
        for k in bid_keys[:self._orderbook_length]:
            price = "%.8f" % k
            quantity = str(data["bids"].get(k))
            bids.append([price, quantity])

        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": data["timestamp"]
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_trade_update(self, data):
        """ Deal with trade data, and publish trade message to EventCenter via TradeEvent.
        """
        symbol = data["symbol"].replace("-", "/")
        if symbol not in self._symbols:
            return
        action = ORDER_ACTION_BUY if data[
            "side"] == "buy" else ORDER_ACTION_SELL
        price = "%.8f" % float(data["price"])
        quantity = "%.8f" % float(data["size"])
        timestamp = int(int(data["time"]) / 1000000)

        # Publish EventTrade.
        trade = {
            "platform": self._platform,
            "symbol": symbol,
            "action": action,
            "price": price,
            "quantity": quantity,
            "timestamp": timestamp
        }
        EventTrade(**trade).publish()
        logger.info("symbol:", symbol, "trade:", trade, caller=self)

    async def do_orderbook_update(self, *args, **kwargs):
        """ Fetch orderbook information."""
        for symbol in self._symbols:
            result, error = await self._rest_api.get_orderbook(
                symbol.replace("/", "-"), self._orderbook_length)
            if error:
                continue
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": result["asks"],
                "bids": result["bids"],
                "timestamp": result["time"]
            }
            EventOrderbook(**orderbook).publish()
            logger.info("symbol:",
                        symbol,
                        "orderbook:",
                        orderbook,
                        caller=self)

            # await 0.1 second before next request.
            await asyncio.sleep(0.1)
Exemplo n.º 26
0
class Huobi:
    """ Huobi Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `huobi`.
            wss: Exchange Websocket host address, default is "wss://api.huobi.pro".
            symbols: Trade pair list, e.g. ["ETH/BTC"].
            channels: channel list, only `orderbook`, `kline` and `trade` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
            price_precious: The price precious, default is 8.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://api.huobi.pro")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._price_precious = kwargs.get("price_precious", 8)

        self._c_to_s = {}  # {"channel": "symbol"}

        url = self._wss + "/ws"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_binary_callback=self.process_binary)
        self._ws.initialize()

    async def connected_callback(self):
        """After create Websocket connection successfully, we will subscribing orderbook/trade/kline events."""
        for ch in self._channels:
            if ch == "kline":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "kline")
                    if not channel:
                        continue
                    kline = {"sub": channel}
                    await self._ws.send(kline)
            elif ch == "orderbook":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "depth")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)
            elif ch == "trade":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "trade")
                    if not channel:
                        continue
                    data = {"sub": channel}
                    await self._ws.send(data)
            else:
                logger.error("channel error! channel:", ch, caller=self)

    async def process_binary(self, msg):
        """ Process binary message that received from Websocket connection.

        Args:
            msg: Binary message received from Websocket connection.
        """
        data = json.loads(gzip.decompress(msg).decode())
        # logger.debug("data:", json.dumps(data), caller=self)
        channel = data.get("ch")
        if not channel:
            if data.get("ping"):
                hb_msg = {"pong": data.get("ping")}
                await self._ws.send(hb_msg)
            return

        symbol = self._c_to_s[channel]

        if channel.find("kline") != -1:
            d = data.get("tick")
            kline = {
                "platform": self._platform,
                "symbol": symbol,
                "open": "%.{}f".format(self._price_precious) % d["open"],
                "high": "%.{}f".format(self._price_precious) % d["high"],
                "low": "%.{}f".format(self._price_precious) % d["low"],
                "close": "%.{}f".format(self._price_precious) % d["close"],
                "volume": "%.{}f".format(self._price_precious) % d["amount"],
                "timestamp": int(data.get("ts")),
                "kline_type": MARKET_TYPE_KLINE
            }
            EventKline(**kline).publish()
            logger.debug("symbol:", symbol, "kline:", kline, caller=self)
        elif channel.find("depth") != -1:
            d = data.get("tick")
            asks, bids = [], []
            for item in d.get("asks")[:self._orderbook_length]:
                price = "%.{}f".format(self._price_precious) % item[0]
                quantity = "%.{}f".format(self._price_precious) % item[1]
                asks.append([price, quantity])
            for item in d.get("bids")[:self._orderbook_length]:
                price = "%.{}f".format(self._price_precious) % item[0]
                quantity = "%.{}f".format(self._price_precious) % item[1]
                bids.append([price, quantity])
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": asks,
                "bids": bids,
                "timestamp": d.get("ts")
            }
            EventOrderbook(**orderbook).publish()
            logger.debug("symbol:",
                         symbol,
                         "orderbook:",
                         orderbook,
                         caller=self)
        elif channel.find("trade") != -1:
            tick = data.get("tick")
            direction = tick["data"][0].get("direction")
            price = tick["data"][0].get("price")
            quantity = tick["data"][0].get("amount")
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action":
                ORDER_ACTION_BUY if direction == "buy" else ORDER_ACTION_SELL,
                "price": "%.{}f".format(self._price_precious) % price,
                "quantity": "%.{}f".format(self._price_precious) % quantity,
                "timestamp": tick.get("ts")
            }
            EventTrade(**trade).publish()
            logger.debug("symbol:", symbol, "trade:", trade, caller=self)
        else:
            logger.error("event error! msg:", msg, caller=self)

    def _symbol_to_channel(self, symbol, channel_type):
        """ Convert symbol to channel.

        Args:
            symbol: Trade pair name.
            channel_type: channel name, kline / ticker / depth.
        """
        if channel_type == "kline":
            channel = "market.{s}.kline.1min".format(
                s=symbol.replace("/", '').lower())
        elif channel_type == "depth":
            channel = "market.{s}.depth.step0".format(
                s=symbol.replace("/", '').lower())
        elif channel_type == "trade":
            channel = "market.{s}.trade.detail".format(
                s=symbol.replace("/", '').lower())
        else:
            logger.error("channel type error! channel type:",
                         channel_type,
                         calle=self)
            return None
        self._c_to_s[channel] = symbol
        return channel
Exemplo n.º 27
0
class Bitmex:
    """ Bitmex Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `bitmex`.
            wss: Exchange Websocket host address, default is `wss://www.bitmex.com`.
            symbols: Symbol list.
            channels: Channel list, only `orderbook` / `trade` / `kline` to be enabled.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://www.bitmex.com")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")

        self._c_to_s = {}  # {"channel": "symbol"}
        url = self._wss + "/realtime"
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

    async def connected_callback(self):
        """After create connection to Websocket server successfully, we will subscribe orderbook/trade/kline event."""
        channels = []
        for ch in self._channels:
            if ch == "orderbook":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "orderBook10")
                    channels.append(channel)
            if ch == "trade":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "trade")
                    channels.append(channel)
            if ch == "kline":
                for symbol in self._symbols:
                    channel = self._symbol_to_channel(symbol, "tradeBin1m")
                    channels.append(channel)
        if channels:
            data = {"op": "subscribe", "args": channels}
            await self._ws.send(data)
            logger.info("subscribe orderbook/trade/kline successfully.",
                        caller=self)

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message data received from Websocket connection.
        """
        logger.debug("msg:", msg, caller=self)
        if not isinstance(msg, dict):
            return

        table = msg.get("table")
        if table == "orderBook10":
            await self.process_orderbook(msg["data"])
        elif table == "trade":
            await self.process_trade(msg["data"])
        elif table == "tradeBin1m":
            await self.process_kline(msg["data"])

    async def process_orderbook(self, data):
        """Process orderbook data and publish OrderbookEvent."""
        for item in data:
            symbol = item.get("symbol")
            orderbook = {
                "platform": self._platform,
                "symbol": symbol,
                "asks": item.get("asks"),
                "bids": item.get("bids"),
                "timestamp": tools.utctime_str_to_mts(item["timestamp"])
            }
            EventOrderbook(**orderbook).publish()
            logger.info("symbol:",
                        symbol,
                        "orderbook:",
                        orderbook,
                        caller=self)

    async def process_kline(self, data):
        """Process kline data and publish KlineEvent."""
        for item in data:
            symbol = item["symbol"]
            kline = {
                "platform": self._platform,
                "symbol": symbol,
                "open": "%.1f" % item["open"],
                "high": "%.1f" % item["high"],
                "low": "%.1f" % item["low"],
                "close": "%.1f" % item["close"],
                "volume": str(item["volume"]),
                "timestamp": tools.utctime_str_to_mts(item["timestamp"]),
                "kline_type": MARKET_TYPE_KLINE
            }
            EventKline(**kline).publish()
            logger.info("symbol:", symbol, "kline:", kline, caller=self)

    async def process_trade(self, data):
        """Process trade data and publish TradeEvent."""
        for item in data:
            symbol = item["symbol"]
            trade = {
                "platform": self._platform,
                "symbol": symbol,
                "action":
                ORDER_ACTION_BUY if item["side"] else ORDER_ACTION_SELL,
                "price": "%.1f" % item["price"],
                "quantity": str(item["size"]),
                "timestamp": tools.utctime_str_to_mts(item["timestamp"])
            }
            EventTrade(**trade).publish()
            logger.info("symbol:", symbol, "trade:", trade, caller=self)

    def _symbol_to_channel(self, symbol, channel_type):
        channel = "{channel_type}:{symbol}".format(channel_type=channel_type,
                                                   symbol=symbol)
        self._c_to_s[channel] = symbol
        return channel
Exemplo n.º 28
0
class BinanceFutureTrade:
    """ Binance Future Trade module. You can initialize trade object with some attributes in kwargs.

    Attributes:
        account: Account name for this trade exchange.
        strategy: What's name would you want to created for you strategy.
        symbol: Symbol name for your trade.
        host: HTTP request host. (default "https://fapi.binance.com")
        wss: Websocket address. (default "wss://fstream.binance.com")
        access_key: Account's ACCESS KEY.
        secret_key Account's SECRET KEY.
        asset_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `asset_update_callback` is like `async def on_asset_update_callback(asset: Asset): pass` and this
            callback function will be executed asynchronous when received AssetEvent.
        order_update_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `order_update_callback` is like `async def on_order_update_callback(order: Order): pass` and this
            callback function will be executed asynchronous when some order state updated.
        init_success_callback: You can use this param to specific a async callback function when you initializing Trade
            object. `init_success_callback` is like `async def on_init_success_callback(success: bool, error: Error, **kwargs): pass`
            and this callback function will be executed asynchronous after Trade module object initialized successfully.
    """
    def __init__(self, **kwargs):
        """Initialize Trade module."""
        e = None
        if not kwargs.get("account"):
            e = Error("param account miss")
        if not kwargs.get("strategy"):
            e = Error("param strategy miss")
        if not kwargs.get("symbol"):
            e = Error("param symbol miss")
        if not kwargs.get("host"):
            kwargs["host"] = "https://fapi.binance.com"
        if not kwargs.get("wss"):
            kwargs["wss"] = "wss://fstream.binance.com"
        if not kwargs.get("access_key"):
            e = Error("param access_key miss")
        if not kwargs.get("secret_key"):
            e = Error("param secret_key miss")
        if e:
            logger.error(e, caller=self)
            if kwargs.get("init_success_callback"):
                SingleTask.run(kwargs["init_success_callback"], False, e)
            return

        self._account = kwargs["account"]
        self._strategy = kwargs["strategy"]
        self._platform = BINANCE_FUTURE
        self._symbol = kwargs["symbol"]
        self._host = kwargs["host"]
        self._wss = kwargs["wss"]
        self._access_key = kwargs["access_key"]
        self._secret_key = kwargs["secret_key"]
        self._asset_update_callback = kwargs.get("asset_update_callback")
        self._order_update_callback = kwargs.get("order_update_callback")
        self._position_update_callback = kwargs.get("position_update_callback")
        self._init_success_callback = kwargs.get("init_success_callback")

        self._ok = False  # Initialize successfully ?

        self._raw_symbol = self._symbol  # Row symbol name, same as Binance Exchange.

        self._listen_key = None  # Listen key for Websocket authentication.
        self._assets = {
        }  # Asset data. e.g. {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }
        self._orders = {}  # Order data. e.g. {order_no: order, ... }
        self._position = Position(self._platform, self._account,
                                  self._strategy, self._symbol)  # 仓位

        # Initialize our REST API client.
        self._rest_api = BinanceFutureRestAPI(self._host, self._access_key,
                                              self._secret_key)

        # Subscribe our AssetEvent.
        if self._asset_update_callback:
            AssetSubscribe(self._platform, self._account,
                           self.on_event_asset_update)

        # Create a loop run task to reset listen key every 20 minutes.
        LoopRunTask.register(self._reset_listen_key, 60 * 20)

        # Create a loop run task to check position information per 1 second.
        LoopRunTask.register(self._check_position_update, 1)

        # Create a loop run task to send ping message to server per 30 seconds.
        # LoopRunTask.register(self._send_heartbeat_msg, 10)

        # Create a coroutine to initialize Websocket connection.
        SingleTask.run(self._init_websocket)

    @property
    def assets(self):
        return copy.copy(self._assets)

    @property
    def orders(self):
        return copy.copy(self._orders)

    @property
    def rest_api(self):
        return self._rest_api

    async def _init_websocket(self):
        """ Initialize Websocket connection.
        """
        # Get listen key first.
        success, error = await self._rest_api.get_listen_key()
        if error:
            e = Error("get listen key failed: {}".format(error))
            logger.error(e, caller=self)
            SingleTask.run(self._init_success_callback, False, e)
            return
        self._listen_key = success["listenKey"]
        uri = "/ws/" + self._listen_key
        url = urljoin(self._wss, uri)
        self._ws = Websocket(url,
                             self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()

    async def _reset_listen_key(self, *args, **kwargs):
        """ Reset listen key.
        """
        if not self._listen_key:
            logger.error("listen key not initialized!", caller=self)
            return
        await self._rest_api.put_listen_key(self._listen_key)
        logger.info("reset listen key success!", caller=self)

    # async def _send_heartbeat_msg(self, *args, **kwargs):
    #     """Send ping to server."""
    #     hb = {"ping": tools.get_cur_timestamp_ms()}
    #     await self._ws.send(hb)

    async def connected_callback(self):
        """ After websocket connection created successfully, pull back all open order information.
        """
        logger.info("Websocket connection authorized successfully.",
                    caller=self)
        order_infos, error = await self._rest_api.get_open_orders(
            self._raw_symbol)
        if error:
            e = Error("get open orders error: {}".format(error))
            SingleTask.run(self._init_success_callback, False, e)
            return
        for order_info in order_infos:
            order_no = "{}_{}".format(order_info["orderId"],
                                      order_info["clientOrderId"])
            if order_info["status"] == "NEW":
                status = ORDER_STATUS_SUBMITTED
            elif order_info["status"] == "PARTIAL_FILLED":
                status = ORDER_STATUS_PARTIAL_FILLED
            elif order_info["status"] == "FILLED":
                status = ORDER_STATUS_FILLED
            elif order_info["status"] == "CANCELED":
                status = ORDER_STATUS_CANCELED
            elif order_info["status"] == "REJECTED":
                status = ORDER_STATUS_FAILED
            elif order_info["status"] == "EXPIRED":
                status = ORDER_STATUS_FAILED
            else:
                logger.warn("unknown status:", order_info, caller=self)
                continue

            info = {
                "platform":
                self._platform,
                "account":
                self._account,
                "strategy":
                self._strategy,
                "order_no":
                order_no,
                "action":
                order_info["side"],
                "order_type":
                order_info["type"],
                "symbol":
                self._symbol,
                "price":
                order_info["price"],
                "quantity":
                order_info["origQty"],
                "remain":
                float(order_info["origQty"]) -
                float(order_info["executedQty"]),
                "status":
                status,
                "trade_type":
                int(order_info["clientOrderId"][-1]),
                "ctime":
                order_info["updateTime"],
                "utime":
                order_info["updateTime"]
            }
            order = Order(**info)
            self._orders[order_no] = order
            SingleTask.run(self._order_update_callback, copy.copy(order))

        self._ok = True
        SingleTask.run(self._init_success_callback, True, None)

    async def create_order(self, action, price, quantity, *args, **kwargs):
        """ Create an order.

        Args:
            action: Trade direction, BUY or SELL.
            price: Price of each contract.
            quantity: The buying or selling quantity.

        Returns:
            order_no: Order ID if created successfully, otherwise it's None.
            error: Error information, otherwise it's None.
        """
        if float(quantity) > 0:
            if action == ORDER_ACTION_BUY:
                trade_type = TRADE_TYPE_BUY_OPEN
            else:
                trade_type = TRADE_TYPE_SELL_CLOSE
        else:
            if action == ORDER_ACTION_BUY:
                trade_type = TRADE_TYPE_BUY_CLOSE
            else:
                trade_type = TRADE_TYPE_SELL_OPEN
        quantity = abs(float(quantity))
        price = tools.float_to_str(price)
        quantity = tools.float_to_str(quantity)
        client_order_id = tools.get_uuid1().replace("-",
                                                    "")[:21] + str(trade_type)
        result, error = await self._rest_api.create_order(
            action, self._raw_symbol, price, quantity, client_order_id)
        if error:
            return None, error
        order_no = "{}_{}".format(result["orderId"], result["clientOrderId"])
        return order_no, None

    async def revoke_order(self, *order_nos):
        """ Revoke (an) order(s).

        Args:
            order_nos: Order id list, you can set this param to 0 or multiple items. If you set 0 param, you can cancel
                all orders for this symbol(initialized in Trade object). If you set 1 param, you can cancel an order.
                If you set multiple param, you can cancel multiple orders. Do not set param length more than 100.

        Returns:
            Success or error, see bellow.
        """
        # If len(order_nos) == 0, you will cancel all orders for this symbol(initialized in Trade object).
        if len(order_nos) == 0:
            order_infos, error = await self._rest_api.get_open_orders(
                self._raw_symbol)
            if error:
                return False, error
            for order_info in order_infos:
                _, error = await self._rest_api.revoke_order(
                    self._raw_symbol, order_info["orderId"],
                    order_info["clientOrderId"])
                if error:
                    return False, error
            return True, None

        # If len(order_nos) == 1, you will cancel an order.
        if len(order_nos) == 1:
            order_id, client_order_id = order_nos[0].split("_")
            success, error = await self._rest_api.revoke_order(
                self._raw_symbol, order_id, client_order_id)
            if error:
                return order_nos[0], error
            else:
                return order_nos[0], None

        # If len(order_nos) > 1, you will cancel multiple orders.
        if len(order_nos) > 1:
            success, error = [], []
            for order_no in order_nos:
                order_id, client_order_id = order_no.split("_")
                _, e = await self._rest_api.revoke_order(
                    self._raw_symbol, order_id, client_order_id)
                if e:
                    error.append((order_no, e))
                else:
                    success.append(order_no)
            return success, error

    async def get_open_order_nos(self):
        """ Get open order no list.
        """
        success, error = await self._rest_api.get_open_orders(self._raw_symbol)
        if error:
            return None, error
        order_nos = []
        for order_info in success:
            order_no = "{}_{}".format(order_info["orderId"],
                                      order_info["clientOrderId"])
            order_nos.append(order_no)
        return order_nos, None

    @async_method_locker("BinanceTrade.process.locker")
    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: message received from Websocket connection.
        """
        logger.debug("msg:", json.dumps(msg), caller=self)
        e = msg.get("e")
        if e == "ORDER_TRADE_UPDATE":  # Order update.
            self._update_order(msg["o"])

    async def _check_position_update(self, *args, **kwargs):
        """Check position update."""
        if not self._ok:
            return
        update = False
        success, error = await self._rest_api.get_position()
        if error:
            return

        position_info = None
        for item in success:
            if item["symbol"] == self._raw_symbol:
                position_info = item
                break

        if not self._position.utime:  # Callback position info when initialized.
            update = True
            self._position.update()
        size = float(position_info["positionAmt"])
        average_price = float(position_info["entryPrice"])
        if size > 0:
            if self._position.long_quantity != size:
                update = True
                self._position.update(0, 0, size, average_price, 0)
        elif size < 0:
            if self._position.short_quantity != abs(size):
                update = True
                self._position.update(abs(size), average_price, 0, 0, 0)
        elif size == 0:
            if self._position.long_quantity != 0 or self._position.short_quantity != 0:
                update = True
                self._position.update()
        if update:
            await self._position_update_callback(copy.copy(self._position))

    def _update_order(self, order_info):
        """ Order update.

        Args:
            order_info: Order information.

        Returns:
            Return order object if or None.
        """
        if order_info["s"] != self._raw_symbol:
            return
        order_no = "{}_{}".format(order_info["i"], order_info["c"])

        if order_info["X"] == "NEW":
            status = ORDER_STATUS_SUBMITTED
        elif order_info["X"] == "PARTIAL_FILLED":
            status = ORDER_STATUS_PARTIAL_FILLED
        elif order_info["X"] == "FILLED":
            status = ORDER_STATUS_FILLED
        elif order_info["X"] == "CANCELED":
            status = ORDER_STATUS_CANCELED
        elif order_info["X"] == "REJECTED":
            status = ORDER_STATUS_FAILED
        elif order_info["X"] == "EXPIRED":
            status = ORDER_STATUS_FAILED
        else:
            return
        order = self._orders.get(order_no)
        if not order:
            info = {
                "platform": self._platform,
                "account": self._account,
                "strategy": self._strategy,
                "order_no": order_no,
                "action": order_info["S"],
                "order_type": order_info["o"],
                "symbol": self._symbol,
                "price": order_info["p"],
                "quantity": order_info["q"],
                "ctime": order_info["T"]
            }
            order = Order(**info)
            self._orders[order_no] = order
        order.remain = float(order_info["q"]) - float(order_info["z"])
        order.avg_price = order_info["L"]
        order.status = status
        order.utime = order_info["T"]
        order.trade_type = int(order_no[-1])
        SingleTask.run(self._order_update_callback, copy.copy(order))

    async def on_event_asset_update(self, asset: Asset):
        """ Asset data update callback.

        Args:
            asset: Asset object.
        """
        self._assets = asset
        SingleTask.run(self._asset_update_callback, asset)
Exemplo n.º 29
0
class Deribit:
    """ Deribit Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `deribit`.
            wss: Exchange Websocket host address, default is `wss://deribit.com`.
            symbols: Symbol list.
            channels: Channel list, only `orderbook` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """
    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = None
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)
        self._access_key = None
        self._secret_key = None
        self._last_msg_ts = tools.get_cur_timestamp_ms()  # 上次接收到消息的时间戳

        for item in config.accounts:
            if item["platform"] == self._platform:
                self._wss = item.get("wss", "wss://deribit.com")
                self._access_key = item["access_key"]
                self._secret_key = item["secret_key"]
        if not self._wss or not self._access_key or not self._access_key:
            logger.error(
                "no find deribit account in ACCOUNTS from config file.",
                caller=self)
            return

        url = self._wss + "/ws/api/v1/"
        self._ws = Websocket(url,
                             connected_callback=self.connected_callback,
                             process_callback=self.process)
        self._ws.initialize()
        LoopRunTask.register(self.send_heartbeat_msg, 10)

    async def connected_callback(self):
        """After create connection to Websocket server successfully, we will subscribe orderbook/trade/kline event."""
        nonce = tools.get_cur_timestamp_ms()
        uri = "/api/v1/private/subscribe"
        params = {"instrument": self._symbols, "event": ["order_book"]}
        sign = self.deribit_signature(nonce, uri, params, self._access_key,
                                      self._secret_key)
        data = {
            "id": "thenextquant",
            "action": uri,
            "arguments": params,
            "sig": sign
        }
        await self._ws.send(data)
        logger.info("subscribe orderbook success.", caller=self)

    async def send_heartbeat_msg(self, *args, **kwargs):
        data = {"action": "/api/v1/public/ping"}
        if not self._ws:
            logger.error("Websocket connection not yeah!", caller=self)
            return
        await self._ws.send(data)

    async def process(self, msg):
        """ Process message that received from Websocket connection.

        Args:
            msg: Message data received from Websocket connection.
        """
        # logger.debug("msg:", msg, caller=self)
        if tools.get_cur_timestamp_ms() <= self._last_msg_ts:
            return
        if not isinstance(msg, dict):
            return
        notifications = msg.get("notifications")
        if not notifications:
            return
        message = notifications[0].get("message")
        if message != "order_book_event":
            return

        symbol = notifications[0].get("result").get("instrument")
        bids = []
        for item in notifications[0].get("result").get(
                "bids")[:self._orderbook_length]:
            b = [item.get("price"), item.get("quantity")]
            bids.append(b)
        asks = []
        for item in notifications[0].get("result").get(
                "asks")[:self._orderbook_length]:
            a = [item.get("price"), item.get("quantity")]
            asks.append(a)
        self._last_msg_ts = tools.get_cur_timestamp_ms()
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": self._last_msg_ts
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    def deribit_signature(self, nonce, uri, params, access_key, access_secret):
        """ 生成signature
        """
        sign = "_=%s&_ackey=%s&_acsec=%s&_action=%s" % (nonce, access_key,
                                                        access_secret, uri)
        for key in sorted(params.keys()):
            sign += "&" + key + "=" + "".join(params[key])
        return "%s.%s.%s" % (
            access_key, nonce,
            base64.b64encode(hashlib.sha256(sign.encode()).digest()).decode())
Exemplo n.º 30
0
class Binance:
    """ Binance Market Server.

    Attributes:
        kwargs:
            platform: Exchange platform name, must be `binance`.
            wss: Exchange Websocket host address, default is `wss://stream.binance.com:9443`.
            symbols: Symbol list.
            channels: Channel list, only `orderbook` / `trade` / `kline` to be enabled.
            orderbook_length: The length of orderbook's data to be published via OrderbookEvent, default is 10.
    """

    def __init__(self, **kwargs):
        self._platform = kwargs["platform"]
        self._wss = kwargs.get("wss", "wss://stream.binance.com:9443")
        self._symbols = list(set(kwargs.get("symbols")))
        self._channels = kwargs.get("channels")
        self._orderbook_length = kwargs.get("orderbook_length", 10)

        self._c_to_s = {}
        self._tickers = {}

        url = self._make_url()
        self._ws = Websocket(url, process_callback=self.process)
        self._ws.initialize()

    def _make_url(self):
        """Generate request url.
        """
        cc = []
        for ch in self._channels:
            if ch == "kline":
                for symbol in self._symbols:
                    c = self._symbol_to_channel(symbol, "kline_1m")
                    cc.append(c)
            elif ch == "orderbook":
                for symbol in self._symbols:
                    c = self._symbol_to_channel(symbol, "depth20")
                    cc.append(c)
            elif ch == "trade":
                for symbol in self._symbols:
                    c = self._symbol_to_channel(symbol, "trade")
                    cc.append(c)
            else:
                logger.error("channel error! channel:", ch, caller=self)
        url = self._wss + "/stream?streams=" + "/".join(cc)
        return url

    async def process(self, msg):
        """Process message that received from Websocket connection.

        Args:
            msg: Message received from Websocket connection.
        """
        # logger.debug("msg:", msg, caller=self)
        if not isinstance(msg, dict):
            return

        channel = msg.get("stream")
        if channel not in self._c_to_s:
            logger.warn("unkown channel, msg:", msg, caller=self)
            return

        symbol = self._c_to_s[channel]
        data = msg.get("data")
        e = data.get("e")

        if e == "kline":
            await self.process_kline(symbol, data)
        elif channel.endswith("depth20"):
            await self.process_orderbook(symbol, data)
        elif e == "trade":
            await self.process_trade(symbol, data)

    async def process_kline(self, symbol, data):
        """Process kline data and publish KlineEvent."""
        kline = {
            "platform": self._platform,
            "symbol": symbol,
            "open": data.get("k").get("o"),
            "high": data.get("k").get("h"),
            "low": data.get("k").get("l"),
            "close": data.get("k").get("c"),
            "volume": data.get("k").get("q"),
            "timestamp": data.get("k").get("t"),
            "kline_type": const.MARKET_TYPE_KLINE
        }
        EventKline(**kline).publish()
        logger.info("symbol:", symbol, "kline:", kline, caller=self)

    async def process_orderbook(self, symbol, data):
        """Process orderbook data and publish OrderbookEvent."""
        bids = []
        asks = []
        for bid in data.get("bids")[:self._orderbook_length]:
            bids.append(bid[:2])
        for ask in data.get("asks")[:self._orderbook_length]:
            asks.append(ask[:2])
        orderbook = {
            "platform": self._platform,
            "symbol": symbol,
            "asks": asks,
            "bids": bids,
            "timestamp": tools.get_cur_timestamp_ms()
        }
        EventOrderbook(**orderbook).publish()
        logger.info("symbol:", symbol, "orderbook:", orderbook, caller=self)

    async def process_trade(self, symbol, data):
        """Process trade data and publish TradeEvent."""
        trade = {
            "platform": self._platform,
            "symbol": symbol,
            "action":  ORDER_ACTION_SELL if data["m"] else ORDER_ACTION_BUY,
            "price": data.get("p"),
            "quantity": data.get("q"),
            "timestamp": data.get("T")
        }
        EventTrade(**trade).publish()
        logger.info("symbol:", symbol, "trade:", trade, caller=self)

    def _symbol_to_channel(self, symbol, channel_type="ticker"):
        channel = "{x}@{y}".format(x=symbol.replace("/", "").lower(), y=channel_type)
        self._c_to_s[channel] = symbol
        return channel