区块键量化交易学习笔记(五)

BitMex websocket:(需要修改,有此是不需要的)

import sys
import websocket
import threading
import traceback
import ssl
from time import sleep
import json
import decimal
import logging
from MidClass.settings import settings
from MidClass.auth.APIKeyAuth import generate_expires, generate_signature
from MidClass.utils.log import setup_custom_logger
from MidClass.utils.math import toNearest
from future.utils import iteritems
from future.standard_library import hooks
with hooks():  # Python 2/3 compat
    from urllib.parse import urlparse, urlunparse


# Connects to Exchange websocket for streaming realtime data.
# The Marketmaker still interacts with this as if it were a REST Endpoint, but now it can get
# much more realtime data without heavily polling the API.
#
# The Websocket offers a bunch of data as raw properties right on the object.
# On connect, it synchronously asks for a push of all this data then returns.
# Right after, the MM can start using its data. It will be updated in realtime, so the MM can
# poll as often as it wants.
class BitMEXWebsocket():

    # Don't grow a table larger than this amount. Helps cap memory usage.
    MAX_TABLE_LEN = 200

    def __init__(self):
        self.logger = logging.getLogger('root')
        self.__reset()

    def __del__(self):
        self.exit()

    def connect(self, endpoint="", symbol="XBTN15", shouldAuth=True):
        '''Connect to the websocket and initialize data stores.'''

        self.logger.debug("Connecting WebSocket.")
        self.symbol = symbol
        self.shouldAuth = shouldAuth

        # We can subscribe right in the connection querystring, so let's build that.
        # Subscribe to all pertinent endpoints
        subscriptions = [sub + ':' + symbol for sub in ["quote", "trade"]]   # 自己动手修改需要返回数据
        subscriptions += ["instrument"]  # We want all of them
        if self.shouldAuth:
            subscriptions += [sub + ':' + symbol for sub in ["order", "execution"]]
            subscriptions += ["margin", "position"]

        # Get WS URL and connect.
        urlParts = list(urlparse(endpoint))
        urlParts[0] = urlParts[0].replace('http', 'ws')
        urlParts[2] = "/realtime?subscribe=" + ",".join(subscriptions)
        wsURL = urlunparse(urlParts)
        self.logger.info("Connecting to %s" % wsURL)
        self.__connect(wsURL)
        self.logger.info('Connected to WS. Waiting for data images, this may take a moment...')

        # Connected. Wait for partials
        self.__wait_for_symbol(symbol)
        if self.shouldAuth:
            self.__wait_for_account()
        self.logger.info('Got all market data. Starting.')

    #
    # Data methods
    #
    def get_instrument(self, symbol):
        instruments = self.data['instrument']
        matchingInstruments = [i for i in instruments if i['symbol'] == symbol]
        if len(matchingInstruments) == 0:
            raise Exception("Unable to find instrument or index with symbol: " + symbol)
        instrument = matchingInstruments[0]
        # Turn the 'tickSize' into 'tickLog' for use in rounding
        # http://stackoverflow.com/a/6190291/832202
        instrument['tickLog'] = decimal.Decimal(str(instrument['tickSize'])).as_tuple().exponent * -1
        return instrument

    def get_ticker(self, symbol):
        '''Return a ticker object. Generated from instrument.'''

        instrument = self.get_instrument(symbol)

        # If this is an index, we have to get the data from the last trade.
        if instrument['symbol'][0] == '.':
            ticker = {}
            ticker['mid'] = ticker['buy'] = ticker['sell'] = ticker['last'] = instrument['markPrice']
        # Normal instrument
        else:
            bid = instrument['bidPrice'] or instrument['lastPrice']
            ask = instrument['askPrice'] or instrument['lastPrice']
            ticker = {
                "last": instrument['lastPrice'],
                "buy": bid,
                "sell": ask,
                "mid": (bid + ask) / 2
            }

        # The instrument has a tickSize. Use it to round values.
        return {k: toNearest(float(v or 0), instrument['tickSize']) for k, v in iteritems(ticker)}

    def funds(self):
        return self.data['margin'][0]

    def market_depth(self, symbol):
        raise NotImplementedError('orderBook is not subscribed; use askPrice and bidPrice on instrument')
        # return self.data['orderBook25'][0]

    def open_orders(self, clOrdIDPrefix):
        orders = self.data['order']
        # Filter to only open orders (leavesQty > 0) and those that we actually placed
        return [o for o in orders if str(o['clOrdID']).startswith(clOrdIDPrefix) and o['leavesQty'] > 0]

    def position(self, symbol):
        positions = self.data['position']
        pos = [p for p in positions if p['symbol'] == symbol]
        if len(pos) == 0:
            # No position found; stub it
            return {'avgCostPrice': 0, 'avgEntryPrice': 0, 'currentQty': 0, 'symbol': symbol}
        return pos[0]

    def recent_trades(self):
        return self.data['trade']

    #
    # Lifecycle methods
    #
    def error(self, err):
        self._error = err
        self.logger.error(err)
        self.exit()

    def exit(self):
        self.exited = True
        self.ws.close()

    #
    # Private methods
    #

    def __connect(self, wsURL):
        '''Connect to the websocket in a thread.'''
        self.logger.debug("Starting thread")

        ssl_defaults = ssl.get_default_verify_paths()
        sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile}
        self.ws = websocket.WebSocketApp(wsURL,
                                         on_message=self.__on_message,
                                         on_close=self.__on_close,
                                         on_open=self.__on_open,
                                         on_error=self.__on_error,
                                         header=self.__get_auth()
                                         )

        setup_custom_logger('websocket', log_level=settings.LOG_LEVEL)
        self.wst = threading.Thread(target=lambda: self.ws.run_forever(sslopt=sslopt_ca_certs))
        self.wst.daemon = True
        self.wst.start()
        self.logger.info("Started thread")

        # Wait for connect before continuing
        conn_timeout = 5
        while (not self.ws.sock or not self.ws.sock.connected) and conn_timeout and not self._error:
            sleep(1)
            conn_timeout -= 1

        if not conn_timeout or self._error:
            self.logger.error("Couldn't connect to WS! Exiting.")
            self.exit()
            sys.exit(1)

    def __get_auth(self):
        '''Return auth headers. Will use API Keys if present in settings.'''

        if self.shouldAuth is False:
            return []

        self.logger.info("Authenticating with API Key.")
        # To auth to the WS using an API key, we generate a signature of a nonce and
        # the WS API endpoint.
        nonce = generate_expires()
        return [
            "api-expires: " + str(nonce),
            "api-signature: " + generate_signature(settings.API_SECRET, 'GET', '/realtime', nonce, ''),
            "api-key:" + settings.API_KEY
        ]

    def __wait_for_account(self):
        '''On subscribe, this data will come down. Wait for it.'''
        # Wait for the keys to show up from the ws
        while not {'margin', 'position', 'order'} <= set(self.data):
            sleep(0.1)

    def __wait_for_symbol(self, symbol):
        '''On subscribe, this data will come down. Wait for it.'''
        while not {'instrument', 'trade', 'quote'} <= set(self.data):
            sleep(0.1)

    def __send_command(self, command, args):
        '''Send a raw command.'''
        self.ws.send(json.dumps({"op": command, "args": args or []}))

    def __on_message(self, message):
        '''Handler for parsing WS messages.'''
        message = json.loads(message)
        self.logger.debug(json.dumps(message))

        table = message['table'] if 'table' in message else None
        action = message['action'] if 'action' in message else None
        try:
            if 'subscribe' in message:
                if message['success']:
                    self.logger.debug("Subscribed to %s." % message['subscribe'])
                else:
                    self.error("Unable to subscribe to %s. Error: \"%s\" Please check and restart." %
                               (message['request']['args'][0], message['error']))
            elif 'status' in message:
                if message['status'] == 400:
                    self.error(message['error'])
                if message['status'] == 401:
                    self.error("API Key incorrect, please check and restart.")
            elif action:

                if table not in self.data:
                    self.data[table] = []

                if table not in self.keys:
                    self.keys[table] = []

                # There are four possible actions from the WS:
                # 'partial' - full table image
                # 'insert'  - new row
                # 'update'  - update row
                # 'delete'  - delete row
                if action == 'partial':
                    self.logger.debug("%s: partial" % table)
                    self.data[table] += message['data']
                    # Keys are communicated on partials to let you know how to uniquely identify
                    # an item. We use it for updates.
                    self.keys[table] = message['keys']
                elif action == 'insert':
                    self.logger.debug('%s: inserting %s' % (table, message['data']))
                    self.data[table] += message['data']

                    # Limit the max length of the table to avoid excessive memory usage.
                    # Don't trim orders because we'll lose valuable state if we do.
                    if table not in ['order', 'orderBookL2'] and len(self.data[table]) > BitMEXWebsocket.MAX_TABLE_LEN:
                        self.data[table] = self.data[table][(BitMEXWebsocket.MAX_TABLE_LEN // 2):]

                elif action == 'update':
                    self.logger.debug('%s: updating %s' % (table, message['data']))
                    # Locate the item in the collection and update it.
                    for updateData in message['data']:
                        item = findItemByKeys(self.keys[table], self.data[table], updateData)
                        if not item:
                            continue  # No item found to update. Could happen before push

                        # Log executions
                        if table == 'order':
                            is_canceled = 'ordStatus' in updateData and updateData['ordStatus'] == 'Canceled'
                            if 'cumQty' in updateData and not is_canceled:
                                contExecuted = updateData['cumQty'] - item['cumQty']
                                if contExecuted > 0:
                                    instrument = self.get_instrument(item['symbol'])
                                    self.logger.info("Execution: %s %d Contracts of %s at %.*f" %
                                             (item['side'], contExecuted, item['symbol'],
                                              instrument['tickLog'], item['price']))

                        # Update this item.
                        item.update(updateData)

                        # Remove canceled / filled orders
                        if table == 'order' and item['leavesQty'] <= 0:
                            self.data[table].remove(item)

                elif action == 'delete':
                    self.logger.debug('%s: deleting %s' % (table, message['data']))
                    # Locate the item in the collection and remove it.
                    for deleteData in message['data']:
                        item = findItemByKeys(self.keys[table], self.data[table], deleteData)
                        self.data[table].remove(item)
                else:
                    raise Exception("Unknown action: %s" % action)
        except:
            self.logger.error(traceback.format_exc())

    def __on_open(self):
        self.logger.debug("Websocket Opened.")

    def __on_close(self):
        self.logger.info('Websocket Closed')
        self.exit()

    def __on_error(self, ws, error):
        if not self.exited:
            self.error(error)

    def __reset(self):
        self.data = {}
        self.keys = {}
        self.exited = False
        self._error = None


def findItemByKeys(keys, table, matchData):
    for item in table:
        matched = True
        for key in keys:
            if item[key] != matchData[key]:
                matched = False
        if matched:
            return item

if __name__ == "__main__":
    # create console handler and set level to debug
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    # create formatter
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # add formatter to ch
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    ws = BitMEXWebsocket()
    ws.logger = logger
    ws.connect("https://testnet.bitmex.com/api/v1")
    while(ws.ws.sock.connected):
        sleep(1)

BitMex REST API:(需要修改,有此是不需要的)

"""Exchange API Connector."""
from __future__ import absolute_import
import requests
import time
import datetime
import json
import base64
import uuid
import logging
from MidClass.auth import APIKeyAuthWithExpires
from MidClass.utils import constants, errors
from MidClass.ws.ws_thread import BitMEXWebsocket
import numpy as np
import pandas as pd

pd.set_option('expand_frame_repr', False)
pd.set_option('display.max_rows', None)


# https://www.bitmex.com/api/explorer/
class Exchange(object):

    """Exchange API Connector."""

    def __init__(self, base_url=None, symbol=None, apiKey=None, apiSecret=None,
                 orderIDPrefix='mm_bitmex_', shouldWSAuth=True, postOnly=False, timeout=7):
        """Init connector."""
        self.logger = logging.getLogger('root')
        self.base_url = base_url
        self.symbol = symbol
        self.postOnly = postOnly
        if (apiKey is None):
            raise Exception("Please set an API key and Secret to get started. See " +
                            "https://github.com/BitMEX/sample-market-maker/#getting-started for more information."
                            )
        self.apiKey = apiKey
        self.apiSecret = apiSecret
        if len(orderIDPrefix) > 13:
            raise ValueError("settings.ORDERID_PREFIX must be at most 13 characters long!")
        self.orderIDPrefix = orderIDPrefix
        self.retries = 0  # initialize counter

        # Prepare HTTPS session
        self.session = requests.Session()
        # These headers are always sent
        self.session.headers.update({'user-agent': 'liquidbot-' + constants.VERSION})
        self.session.headers.update({'content-type': 'application/json'})
        self.session.headers.update({'accept': 'application/json'})

        # Create websocket for streaming data
        self.ws = BitMEXWebsocket()
        self.ws.connect(base_url, symbol, shouldAuth=shouldWSAuth)

        self.timeout = timeout

    def __del__(self):
        self.exit()

    def exit(self):
        self.ws.exit()

    #
    # Public methods
    #
    def ticker_data(self, symbol=None):
        """Get ticker data."""
        if symbol is None:
            symbol = self.symbol
        return self.ws.get_ticker(symbol)

    def instrument(self, symbol):
        """Get an instrument's details."""
        return self.ws.get_instrument(symbol)

    def instruments(self, filter=None):
        query = {}
        if filter is not None:
            query['filter'] = json.dumps(filter)
        return self._curl_bitmex(path='instrument', query=query, verb='GET')

    def kline(self, symbol, interval='5m'):
        """Get the Kline"""
        path = "trade/bucketed"
        end_time = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
        query = {
            'binSize': interval,
            'partial': 'false',
            'symbol': symbol,
            'count': 200,
            'reverse': 'true',
            'endTime': end_time
        }
        content = self._curl_bitmex(path=path, query=query, verb='GET')
        dataframe = pd.DataFrame(content, dtype=float)
        dataframe.sort_values(by='timestamp', axis=0, ascending=True, inplace=True)
        dataframe.reset_index(drop=True, inplace=True)
        dataframe['candle_begin_time'] = (pd.to_datetime(dataframe['timestamp']).values - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's') - pd.Timedelta(interval).seconds
        # dataframe['candle_begin_time'] = pd.to_datetime(dataframe['candle_begin_time'], format="%Y-%m-%dT%H:%M:%S.%fZ", errors='coerce') 
        dataframe['candle_begin_time_GMT8'] = pd.to_datetime(dataframe['candle_begin_time'], unit='s') + timedelta(hours=8)
        dataframe = dataframe[['candle_begin_time_GMT8', 'open', 'high', 'low', 'close', 'homeNotional']]
        dataframe.rename(columns={'homeNotional': 'volume'}, inplace=True)
        return dataframe


    def market_depth(self, symbol):
        """Get market depth / orderbook."""
        return self.ws.market_depth(symbol)

    def recent_trades(self):
        """Get recent trades.

        Returns
        -------
        A list of dicts:
              {u'amount': 60,
               u'date': 1306775375,
               u'price': 8.7401099999999996,
               u'tid': u'93842'},

        """
        return self.ws.recent_trades()

    #
    # Authentication required methods
    #
    def authentication_required(fn):
        """Annotation for methods that require auth."""
        def wrapped(self, *args, **kwargs):
            if not (self.apiKey):
                msg = "You must be authenticated to use this method"
                raise errors.AuthenticationError(msg)
            else:
                return fn(self, *args, **kwargs)
        return wrapped

    @authentication_required
    def funds(self):
        """Get your current balance."""
        return self.ws.funds()

    @authentication_required
    def position(self, symbol):
        """Get your open position."""
        return self.ws.position(symbol)

    @authentication_required
    def isolate_margin(self, symbol, leverage, rethrow_errors=False):
        """Set the leverage on an isolated margin position"""
        path = "position/leverage"
        postdict = {
            'symbol': symbol,
            'leverage': leverage
        }
        return self._curl_bitmex(path=path, postdict=postdict, verb="POST", rethrow_errors=rethrow_errors)

    @authentication_required
    def delta(self):
        return self.position(self.symbol)['homeNotional']

    @authentication_required
    def buy(self, quantity, price):
        """Place a buy order.

        Returns order object. ID: orderID
        """
        return self.place_order(quantity, price)

    @authentication_required
    def sell(self, quantity, price):
        """Place a sell order.

        Returns order object. ID: orderID
        """
        return self.place_order(-quantity, price)

    @authentication_required
    def place_order(self, quantity, price):
        """Place an order."""
        if price < 0:
            raise Exception("Price must be positive.")

        endpoint = "order"
        # Generate a unique clOrdID with our prefix so we can identify it.
        clOrdID = self.orderIDPrefix + base64.b64encode(uuid.uuid4().bytes).decode('utf8').rstrip('=\n')
        postdict = {
            'symbol': self.symbol,
            'orderQty': quantity,
            'price': price,
            'clOrdID': clOrdID
        }
        return self._curl_bitmex(path=endpoint, postdict=postdict, verb="POST")

    @authentication_required
    def amend_bulk_orders(self, orders):
        """Amend multiple orders."""
        # Note rethrow; if this fails, we want to catch it and re-tick
        return self._curl_bitmex(path='order/bulk', postdict={'orders': orders}, verb='PUT', rethrow_errors=True)

    @authentication_required
    def create_bulk_orders(self, orders):
        """Create multiple orders."""
        for order in orders:
            order['clOrdID'] = self.orderIDPrefix + base64.b64encode(uuid.uuid4().bytes).decode('utf8').rstrip('=\n')
            order['symbol'] = self.symbol
            if self.postOnly:
                order['execInst'] = 'ParticipateDoNotInitiate'
        return self._curl_bitmex(path='order/bulk', postdict={'orders': orders}, verb='POST')

    @authentication_required
    def open_orders(self):
        """Get open orders."""
        return self.ws.open_orders(self.orderIDPrefix)

    @authentication_required
    def http_open_orders(self):
        """Get open orders via HTTP. Used on close to ensure we catch them all."""
        path = "order"
        orders = self._curl_bitmex(
            path=path,
            query={
                'filter': json.dumps({'ordStatus.isTerminated': False, 'symbol': self.symbol}),
                'count': 500
            },
            verb="GET"
        )
        # Only return orders that start with our clOrdID prefix.
        return [o for o in orders if str(o['clOrdID']).startswith(self.orderIDPrefix)]

    @authentication_required
    def cancel(self, orderID):
        """Cancel an existing order."""
        path = "order"
        postdict = {
            'orderID': orderID,
        }
        return self._curl_bitmex(path=path, postdict=postdict, verb="DELETE")

    @authentication_required
    def withdraw(self, amount, fee, address):
        path = "user/requestWithdrawal"
        postdict = {
            'amount': amount,
            'fee': fee,
            'currency': 'XBt',
            'address': address
        }
        return self._curl_bitmex(path=path, postdict=postdict, verb="POST", max_retries=0)

    def _curl_bitmex(self, path, query=None, postdict=None, timeout=None, verb=None, rethrow_errors=False,
                     max_retries=None):
        """Send a request to Exchange Servers."""
        # Handle URL
        url = self.base_url + path

        if timeout is None:
            timeout = self.timeout

        # Default to POST if data is attached, GET otherwise
        if not verb:
            verb = 'POST' if postdict else 'GET'

        # By default don't retry POST or PUT. Retrying GET/DELETE is okay because they are idempotent.
        # In the future we could allow retrying PUT, so long as 'leavesQty' is not used (not idempotent),
        # or you could change the clOrdID (set {"clOrdID": "new", "origClOrdID": "old"}) so that an amend
        # can't erroneously be applied twice.
        if max_retries is None:
            max_retries = 0 if verb in ['POST', 'PUT'] else 3

        # Auth: API Key/Secret
        auth = APIKeyAuthWithExpires(self.apiKey, self.apiSecret)

        def exit_or_throw(e):
            if rethrow_errors:
                raise e
            else:
                exit(1)

        def retry():
            self.retries += 1
            if self.retries > max_retries:
                raise Exception("Max retries on %s (%s) hit, raising." % (path, json.dumps(postdict or '')))
            return self._curl_bitmex(path, query, postdict, timeout, verb, rethrow_errors, max_retries)

        # Make the request
        response = None
        try:
            self.logger.info("sending req to %s: %s" % (url, json.dumps(postdict or query or '')))
            req = requests.Request(verb, url, json=postdict, auth=auth, params=query)
            prepped = self.session.prepare_request(req)
            response = self.session.send(prepped, timeout=timeout)
            # Make non-200s throw
            response.raise_for_status()

        except requests.exceptions.HTTPError as e:
            if response is None:
                raise e

            # 401 - Auth error. This is fatal.
            if response.status_code == 401:
                self.logger.error("API Key or Secret incorrect, please check and restart.")
                self.logger.error("Error: " + response.text)
                if postdict:
                    self.logger.error(postdict)
                # Always exit, even if rethrow_errors, because this is fatal
                exit(1)

            # 404, can be thrown if order canceled or does not exist.
            elif response.status_code == 404:
                if verb == 'DELETE':
                    self.logger.error("Order not found: %s" % postdict['orderID'])
                    return
                self.logger.error("Unable to contact the Exchange API (404). " +
                                  "Request: %s \n %s" % (url, json.dumps(postdict)))
                exit_or_throw(e)

            # 429, ratelimit; cancel orders & wait until X-RateLimit-Reset
            elif response.status_code == 429:
                self.logger.error("Ratelimited on current request. Sleeping, then trying again. Try fewer " +
                                  "order pairs or contact [email protected] to raise your limits. " +
                                  "Request: %s \n %s" % (url, json.dumps(postdict)))

                # Figure out how long we need to wait.
                ratelimit_reset = response.headers['X-RateLimit-Reset']
                to_sleep = int(ratelimit_reset) - int(time.time())
                reset_str = datetime.datetime.fromtimestamp(int(ratelimit_reset)).strftime('%X')

                # We're ratelimited, and we may be waiting for a long time. Cancel orders.
                self.logger.warning("Canceling all known orders in the meantime.")
                self.cancel([o['orderID'] for o in self.open_orders()])

                self.logger.error("Your ratelimit will reset at %s. Sleeping for %d seconds." % (reset_str, to_sleep))
                time.sleep(to_sleep)

                # Retry the request.
                return retry()

            # 503 - Exchange temporary downtime, likely due to a deploy. Try again
            elif response.status_code == 503:
                self.logger.warning("Unable to contact the Exchange API (503), retrying. " +
                                    "Request: %s \n %s" % (url, json.dumps(postdict)))
                time.sleep(3)
                return retry()

            elif response.status_code == 400:
                error = response.json()['error']
                message = error['message'].lower() if error else ''

                # Duplicate clOrdID: that's fine, probably a deploy, go get the order(s) and return it
                if 'duplicate clordid' in message:
                    orders = postdict['orders'] if 'orders' in postdict else postdict

                    IDs = json.dumps({'clOrdID': [order['clOrdID'] for order in orders]})
                    orderResults = self._curl_bitmex('/order', query={'filter': IDs}, verb='GET')

                    for i, order in enumerate(orderResults):
                        if (
                                order['orderQty'] != abs(postdict['orderQty']) or
                                order['side'] != ('Buy' if postdict['orderQty'] > 0 else 'Sell') or
                                order['price'] != postdict['price'] or
                                order['symbol'] != postdict['symbol']):
                            raise Exception('Attempted to recover from duplicate clOrdID, but order returned from API ' +
                                            'did not match POST.\nPOST data: %s\nReturned order: %s' % (
                                                json.dumps(orders[i]), json.dumps(order)))
                    # All good
                    return orderResults

                elif 'insufficient available balance' in message:
                    self.logger.error('Account out of funds. The message: %s' % error['message'])
                    exit_or_throw(Exception('Insufficient Funds'))


            # If we haven't returned or re-raised yet, we get here.
            self.logger.error("Unhandled Error: %s: %s" % (e, response.text))
            self.logger.error("Endpoint was: %s %s: %s" % (verb, path, json.dumps(postdict)))
            exit_or_throw(e)

        except requests.exceptions.Timeout as e:
            # Timeout, re-run this request
            self.logger.warning("Timed out on request: %s (%s), retrying..." % (path, json.dumps(postdict or '')))
            return retry()

        except requests.exceptions.ConnectionError as e:
            self.logger.warning("Unable to contact the Exchange API (%s). Please check the URL. Retrying. " +
                                "Request: %s %s \n %s" % (e, url, json.dumps(postdict)))
            time.sleep(1)
            return retry()

        # Reset retry counter on success
        self.retries = 0

        return response.json()


使用BitMex官方交易机器人:https://github.com/BitMEX/sample-market-maker

文件结构:

settings.py

custom_strategy.py

market_maket

-----_init_.py

-----_settings_base.py

------bitmex.py

------market_maker.py

------settings.py

-----auth

        ------_init_.py

        ------AccessTokenAuth.py

        ------APIKeyAuth.py

        ------APIKeyAuthWithExpires.py

-----utils

        ------_init_.py

        ------constants.py

        ------dotdict.py

        ------errors.py

        ------log.py

        ------math.py

-----ws

        ------_init_.py

        ------ws_thread.py

首先settings.py的设置

基本参数等,DRY_RUN = True不能下单,DRY_RUN = False能下单

再修改custom_strategy.py(放到与settings.py参数设置的同级主目录)

对class CustomOrderManager(OrderManager):修改为自己的策略

添加主函数执行

def run() -> None:
    order_manager = CustomOrderManager()

    # Try/except just keeps ctrl-c from printing an ugly stacktrace
    try:
        order_manager.run_loop()
    except (KeyboardInterrupt, SystemExit):
        sys.exit()


if __name__ == '__main__':
    run()

别人用ccxt写的代码供参考

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver import DesiredCapabilities
from selenium.common.exceptions import NoSuchElementException
from multiprocessing import Process
from subprocess import Popen
from time import gmtime, strftime
from os import system
import subprocess
import pyperclip
import datetime
import time
import os
import sys 
import ccxt
import ccxt.async as ccxt_async
import threading
import json
from threading import Thread
import asyncio
import math


target_multiplier = 1
post_only = {'execInst': 'ParticipateDoNotInitiate','id':123}

bitmex = ccxt.bitmex({
    'apiKey': '',
    'secret': '',
})

market = 'BTC/USD'
bitmex.enableRateLimit = False
print(ccxt.__version__)
#print (dir (ccxt.bitmex ()))
#time.sleep(999)

def trader():
        while True:
                #bitmex.enableRateLimit = True
                while True:
                        try:
                                time.sleep(2) ## Global sleep
                                #bitmex.privateDeleteOrderAll () ## Clear orders
                                orderbook = bitmex.fetch_order_book (market) 
                                break
                        except (ccxt.ExchangeError, ccxt.NetworkError) as error: 
                                print('Got an error in fetch orderbook or clearing orders', type(error).__name__, error.args)
                                time.sleep(1)
                                continue 
                    
                bid = orderbook['bids'][0][0] if len (orderbook['bids']) > 0 else None
                ask = orderbook['asks'][0][0] if len (orderbook['asks']) > 0 else None
                cur_price_average = (ask + bid) / 2

                #cur_price_average = bitmex.fetch_ticker(market)
                print('======================================')
                print('Current averaged price(XBT/USD):', cur_price_average)

                while True:
                        try:
                                freebalance = bitmex.fetch_balance() ##error 502
                                break
                        except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                print('Got an error in fetch balance', type(error).__name__, error.args)
                                time.sleep(1)
                                continue
                        
                current_target1x = cur_price_average * freebalance['BTC']['free']        
                print('Current freebalance(XBT):', freebalance['BTC']['free'] if 'BTC' in freebalance else 'Empty BTC account')
                print('Current target 1x Leverage:', current_target1x)
                

                while True:
                        try:
                                #current_bitmex_position = bitmex.private_get_position()[0]['currentQty']

                                current_bitmex_position = bitmex.private_get_position()
                                current_bitmex_position = [item for item in current_bitmex_position if item['symbol'] == 'XBTUSD'][0]['currentQty']
                                print('Current bitmex position($):',current_bitmex_position)
                                break
                        except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                print('Got an error getting private position', type(error).__name__, error.args)
                                time.sleep(1)
                                continue
                while True:
                        try:
                                contracts_amount=int(input('Input conract amount:'))
                                break
                        except ValueError:
                                print ('Not a number')
                                continue

                #LONG SIDE
                if (contracts_amount > 0):
                        while True:
                                try:
                                        #bitmex.privateDeleteOrderAll ()
                                        orderbookLong = bitmex.fetch_order_book (market)
                                        BasicBid = orderbookLong['bids'][0][0] if len (orderbook['bids']) > 0 else None
                                        resultLong = bitmex.create_order(symbol='BTC/USD', type='limit', side='buy', amount=contracts_amount, price=BasicBid, params = post_only)
                                        break
                                except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                        print('Got an error placing Long', type(error).__name__, error.args)
                                        time.sleep(1)
                                        continue
                                
                        while(contracts_amount > 0):
                                print("BITMEX Goind LONG with:", contracts_amount)
                                try:
                                        while True:
                                                CurrentOpenOrderPrice = bitmex.fetchOpenOrders()[0]['price']
                                                print('CurrentOpenOrderPrice:',CurrentOpenOrderPrice)
                                                orderbookLong = bitmex.fetch_order_book (market)
                                                BestBid = orderbookLong['bids'][0][0] if len (orderbook['bids']) > 0 else None
                                                print('BestBid:',BestBid)
                                                if (BestBid != CurrentOpenOrderPrice):
                                                        bitmex.edit_order(resultLong['id'],'BTC/USD','limit',resultLong['side'],price=BestBid)
                                                        print('======Price changed to', BestBid)
                                                        time.sleep(0.5)
                                                tempLongID = resultLong['id']
                                                print('resultLong id',resultLong['id'])
                                                time.sleep(1)
                                        if (resultLong['id'] != tempLongID):
                                                        break
                                except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                        print('Got an error in amending price Long:', type(error).__name__, error.args)
                                        time.sleep(1)
                                        continue
                                except (IndexError) as error:
                                        print('Order Long filled', type(error).__name__, error.args)
                                        break
                #SHORT SIDE                
                elif (contracts_amount < 0):
                        while True:
                                try:
                                        #bitmex.privateDeleteOrderAll ()
                                        orderbookShort = bitmex.fetch_order_book (market)
                                        BasicAsk = orderbookShort['asks'][0][0] if len (orderbook['asks']) > 0 else None
                                        resultShort = bitmex.create_order(symbol='BTC/USD', type='limit', side='sell', amount=contracts_amount, price=BasicAsk, params = post_only)
                                        break
                                except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                        print('Got an error placing Short', type(error).__name__, error.args)
                                        time.sleep(1)
                                        continue
                        while(contracts_amount < 0):
                                #bitmex.enableRateLimit = True
                                print("BITMEX Goind SHORT with:", contracts_amount)
                                try:
                                        while True:
                                                CurrentOpenOrderPrice = bitmex.fetchOpenOrders()[0]['price']
                                                print('CurrentOpenOrderPrice:',CurrentOpenOrderPrice)
                                                orderbook = bitmex.fetch_order_book (market)
                                                BestAsk = orderbook['asks'][0][0] if len (orderbook['asks']) > 0 else None
                                                print('BestAsk:',BestAsk)
                                                if (BestAsk != CurrentOpenOrderPrice):
                                                        bitmex.edit_order(resultShort['id'],'BTC/USD','limit',resultShort['side'],price=BestAsk)
                                                        print('======Price changed to', BestAsk)
                                                        time.sleep(0.5)
                                                tempShortID = resultShort['id']
                                                print('resultShort id',resultShort['id'])
                                                time.sleep(1)
                                        if (resultShort['id'] != tempShortID):
                                                        break
                                except (ccxt.ExchangeError, ccxt.NetworkError) as error:
                                        print('Got an error in amending price Short:', type(error).__name__, error.args)
                                        time.sleep(1)
                                        continue
                                except (IndexError) as error:
                                        print('Order Short filled', type(error).__name__, error.args)
                                        break
                

if __name__ == '__main__':
                        Thread(target = trader).start()

 

你可能感兴趣的:(REST,API,Websocket,应用)