commit
851c22528f
1
vnpy/gateway/coinbase/__init__.py
Normal file
1
vnpy/gateway/coinbase/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .coinbase_gateway import CoinbaseGateway
|
885
vnpy/gateway/coinbase/coinbase_gateway.py
Normal file
885
vnpy/gateway/coinbase/coinbase_gateway.py
Normal file
@ -0,0 +1,885 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from copy import copy
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
import base64
|
||||
import uuid
|
||||
|
||||
from typing import List, Sequence
|
||||
|
||||
from requests import ConnectionError
|
||||
|
||||
from vnpy.event import Event
|
||||
from vnpy.api.rest import Request, RestClient
|
||||
from vnpy.api.websocket import WebsocketClient
|
||||
from vnpy.trader.event import EVENT_TIMER
|
||||
from vnpy.trader.constant import (
|
||||
Direction,
|
||||
Exchange,
|
||||
OrderType,
|
||||
Product,
|
||||
Status,
|
||||
Offset,
|
||||
Interval
|
||||
)
|
||||
from vnpy.trader.gateway import BaseGateway
|
||||
from vnpy.trader.object import (
|
||||
TickData,
|
||||
OrderData,
|
||||
TradeData,
|
||||
PositionData,
|
||||
AccountData,
|
||||
ContractData,
|
||||
BarData,
|
||||
OrderRequest,
|
||||
CancelRequest,
|
||||
SubscribeRequest,
|
||||
HistoryRequest
|
||||
)
|
||||
|
||||
REST_HOST = "https://api.pro.coinbase.com"
|
||||
WEBSOCKET_HOST = "wss://ws-feed.pro.coinbase.com"
|
||||
SANDBOX_REST_HOST = "https://api-public.sandbox.pro.coinbase.com"
|
||||
SANDBOX_WEBSOCKET_HOST = "wss://ws-feed-public.sandbox.pro.coinbase.com"
|
||||
|
||||
DIRECTION_VT2COINBASE = {Direction.LONG: "buy", Direction.SHORT: "sell"}
|
||||
DIRECTION_COINBASE2VT = {v: k for k, v in DIRECTION_VT2COINBASE.items()}
|
||||
|
||||
STOP_VT2COINBASE = {Direction.LONG: "entry", Direction.SHORT: "loss"}
|
||||
|
||||
ORDERTYPE_VT2COINBASE = {
|
||||
OrderType.LIMIT: "limit",
|
||||
OrderType.MARKET: "market",
|
||||
}
|
||||
ORDERTYPE_COINBASE2VT = {v: k for k, v in ORDERTYPE_VT2COINBASE.items()}
|
||||
|
||||
INTERVAL_VT2COINBASE = {
|
||||
Interval.MINUTE: "1m",
|
||||
Interval.HOUR: "1h",
|
||||
Interval.DAILY: "1d",
|
||||
}
|
||||
|
||||
TIMEDELTA_MAP = {
|
||||
Interval.MINUTE: timedelta(minutes=1),
|
||||
Interval.HOUR: timedelta(hours=1),
|
||||
Interval.DAILY: timedelta(days=1),
|
||||
}
|
||||
|
||||
cancelDict = {} # orderid:cancelreq
|
||||
orderDict = {} # sysid:order
|
||||
orderSysDict = {} # orderid:sysid
|
||||
|
||||
|
||||
class CoinbaseGateway(BaseGateway):
|
||||
"""
|
||||
VN Trader Gateway for coinbase connection
|
||||
"""
|
||||
|
||||
default_setting = {
|
||||
"ID": "",
|
||||
"Secret": "",
|
||||
"passphrase": "",
|
||||
"会话数": 3,
|
||||
"server": ["REAL", "SANDBOX"],
|
||||
"proxy_host": "",
|
||||
"proxy_port": "",
|
||||
}
|
||||
exchanges = [Exchange.COINBASE]
|
||||
|
||||
def __init__(self, event_engine):
|
||||
"""Constructor"""
|
||||
super(CoinbaseGateway, self).__init__(event_engine, "COINBASE")
|
||||
self.rest_api = CoinbaseRestApi(self)
|
||||
self.ws_api = CoinbaseWebsocketApi(self)
|
||||
|
||||
self.rest_api_inited = False
|
||||
|
||||
self.product_id = []
|
||||
self.received_instrument = False
|
||||
|
||||
event_engine.register(EVENT_TIMER, self.process_timer_event)
|
||||
|
||||
def connect(self, setting: dict):
|
||||
""""""
|
||||
key = setting["ID"]
|
||||
secret = setting["Secret"]
|
||||
session_number = setting["会话数"]
|
||||
proxy_host = setting["proxy_host"]
|
||||
proxy_port = setting["proxy_port"]
|
||||
server = setting['server']
|
||||
passphrase = setting['passphrase']
|
||||
|
||||
if proxy_port.isdigit():
|
||||
proxy_port = int(proxy_port)
|
||||
else:
|
||||
proxy_port = 0
|
||||
|
||||
self.rest_api.connect(key, secret, passphrase, session_number, server,
|
||||
proxy_host, proxy_port)
|
||||
while not self.received_instrument:
|
||||
time.sleep(0.5)
|
||||
|
||||
self.write_log("合约查询成功")
|
||||
self.ws_api.connect(
|
||||
key,
|
||||
secret,
|
||||
passphrase,
|
||||
server,
|
||||
proxy_host,
|
||||
proxy_port)
|
||||
|
||||
def subscribe(self, req: SubscribeRequest):
|
||||
""""""
|
||||
self.ws_api.subscribe(req)
|
||||
|
||||
def send_order(self, req: OrderRequest):
|
||||
""""""
|
||||
return self.rest_api.send_order(req)
|
||||
|
||||
def cancel_order(self, req: CancelRequest):
|
||||
""""""
|
||||
self.rest_api.cancel_order(req)
|
||||
|
||||
def query_account(self):
|
||||
""""""
|
||||
return self.rest_api.qry_account()
|
||||
|
||||
def query_position(self):
|
||||
"""
|
||||
Query holding positions.
|
||||
"""
|
||||
pass
|
||||
|
||||
def query_history(self, req: HistoryRequest):
|
||||
"""
|
||||
Query bar history data.
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
""""""
|
||||
self.rest_api.stop()
|
||||
self.ws_api.stop()
|
||||
|
||||
def process_timer_event(self, event: Event):
|
||||
""""""
|
||||
self.rest_api.reset_rate_limit()
|
||||
if self.rest_api_inited:
|
||||
self.init_query()
|
||||
|
||||
def init_query(self):
|
||||
""""""
|
||||
self.rest_api.qry_account()
|
||||
|
||||
|
||||
class CoinbaseWebsocketApi(WebsocketClient):
|
||||
"""
|
||||
Coinbase WEBSOCKET API
|
||||
"""
|
||||
|
||||
def __init__(self, gateway: BaseGateway):
|
||||
""""""
|
||||
super(CoinbaseWebsocketApi, self).__init__()
|
||||
|
||||
self.gateway = gateway
|
||||
self.gateway_name = gateway.gateway_name
|
||||
|
||||
self.key = ""
|
||||
self.secret = ""
|
||||
self.passphrase = ""
|
||||
|
||||
self.callbacks = {
|
||||
"ticker": self.on_orderbook,
|
||||
"l2update": self.on_orderbook,
|
||||
"snapshot": self.on_orderbook,
|
||||
"received": self.on_order_received,
|
||||
"open": self.on_order_open,
|
||||
"done": self.on_order_done,
|
||||
"match": self.on_order_match,
|
||||
}
|
||||
self.ticks = {}
|
||||
|
||||
self.accounts = {}
|
||||
self.orderbooks = {}
|
||||
|
||||
def connect(
|
||||
self,
|
||||
key: str,
|
||||
secret: str,
|
||||
passphrase: str,
|
||||
server: str,
|
||||
proxy_host: str,
|
||||
proxy_port: int):
|
||||
""""""
|
||||
self.gateway.write_log("开始连接ws接口")
|
||||
self.key = key
|
||||
self.secret = secret.encode()
|
||||
self.passphrase = passphrase
|
||||
|
||||
if server == "REAL":
|
||||
self.init(WEBSOCKET_HOST, proxy_host, proxy_port)
|
||||
else:
|
||||
self.init(SANDBOX_WEBSOCKET_HOST, proxy_host, proxy_port)
|
||||
self.start()
|
||||
|
||||
def subscribe(self, req: SubscribeRequest):
|
||||
""""""
|
||||
symbol = req.symbol
|
||||
exchange = req.exchange
|
||||
|
||||
orderbook = OrderBook(symbol, exchange, self.gateway)
|
||||
self.orderbooks[symbol] = orderbook
|
||||
|
||||
sub_req = {
|
||||
"type": "subscribe",
|
||||
"product_ids": [symbol],
|
||||
"channels": ["user", "level2", "ticker"],
|
||||
}
|
||||
|
||||
timestamp = str(time.time())
|
||||
message = timestamp + 'GET' + '/users/self/verify'
|
||||
auth_headers = get_auth_header(
|
||||
timestamp,
|
||||
message,
|
||||
self.key,
|
||||
self.secret,
|
||||
self.passphrase)
|
||||
sub_req['signature'] = auth_headers['CB-ACCESS-SIGN']
|
||||
sub_req['key'] = auth_headers['CB-ACCESS-KEY']
|
||||
sub_req['passphrase'] = auth_headers['CB-ACCESS-PASSPHRASE']
|
||||
sub_req['timestamp'] = auth_headers['CB-ACCESS-TIMESTAMP']
|
||||
|
||||
self.send_packet(sub_req)
|
||||
|
||||
def on_connected(self):
|
||||
"""
|
||||
callback when connection is established
|
||||
"""
|
||||
self.gateway.write_log("Websocket API连接成功")
|
||||
|
||||
def on_disconnected(self):
|
||||
""""""
|
||||
self.gateway.write_log("Websocket API连接断开")
|
||||
|
||||
def on_packet(self, packet: dict):
|
||||
"""
|
||||
callback when data is received and unpacked
|
||||
"""
|
||||
|
||||
if packet['type'] == 'error':
|
||||
self.gateway.write_log("Websocket API报错: %s" % packet['message'])
|
||||
self.gateway.write_log("Websocket API报错原因是: %s" % packet['reason'])
|
||||
self.active = False
|
||||
|
||||
else:
|
||||
callback = self.callbacks.get(packet['type'], None)
|
||||
if callback:
|
||||
if packet['type'] not in ['ticker', 'snapshot', 'l2update']:
|
||||
callback(packet)
|
||||
else:
|
||||
product_id = packet['product_id']
|
||||
callback(packet, product_id)
|
||||
|
||||
def on_orderbook(self, packet: dict, product_id: str):
|
||||
"""
|
||||
Call back when data is used to update orderbook
|
||||
"""
|
||||
orderbook = self.orderbooks[product_id]
|
||||
orderbook.on_message(packet)
|
||||
|
||||
def on_order_received(self, packet: dict):
|
||||
"""
|
||||
Call back when order is received by Coinbase
|
||||
"""
|
||||
client_oid = packet['client_oid']
|
||||
sysid = packet['order_id']
|
||||
|
||||
order = OrderData(
|
||||
symbol=packet['product_id'],
|
||||
exchange=Exchange.COINBASE,
|
||||
type=ORDERTYPE_COINBASE2VT[packet['order_type']],
|
||||
orderid=sysid,
|
||||
direction=DIRECTION_COINBASE2VT[packet['side']],
|
||||
price=float(packet['price']),
|
||||
volume=float(packet['size']),
|
||||
time=packet['time'],
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
|
||||
order.traded = 0
|
||||
order.status = Status.NOTTRADED
|
||||
|
||||
orderSysDict[client_oid] = sysid
|
||||
orderDict[sysid] = order
|
||||
|
||||
if client_oid in cancelDict:
|
||||
req = cancelDict[client_oid]
|
||||
self.gateway.cancel_order(req)
|
||||
|
||||
self.gateway.on_order(copy(order))
|
||||
|
||||
def on_order_open(self, packet: dict):
|
||||
"""
|
||||
Call back when the order is on the orderbook
|
||||
"""
|
||||
orderid = packet['order_id']
|
||||
order = orderDict.get(orderid)
|
||||
order.traded = float(order.volume) - float(packet['remaining_size'])
|
||||
if order.traded:
|
||||
order.status = Status.PARTTRADED
|
||||
|
||||
self.gateway.on_order(copy(order))
|
||||
|
||||
def on_order_done(self, packet: dict):
|
||||
"""
|
||||
Call back when the order is done
|
||||
"""
|
||||
order = orderDict.get(packet['order_id'], None)
|
||||
if not order:
|
||||
return
|
||||
order.traded = order.volume - float(packet['remaining_size'])
|
||||
if packet['reason'] == 'filled':
|
||||
order.status = Status.ALLTRADED
|
||||
else:
|
||||
order.status = Status.CANCELLED
|
||||
self.gateway.on_order(copy(order))
|
||||
|
||||
def on_order_match(self, packet: dict):
|
||||
""""""
|
||||
if packet['maker_order_id'] in orderDict:
|
||||
order = orderDict[packet['maker_order_id']]
|
||||
else:
|
||||
order = orderDict[packet['taker_order_id']]
|
||||
|
||||
trade = TradeData(
|
||||
symbol=packet['product_id'],
|
||||
exchange=Exchange.COINBASE,
|
||||
orderid=order.orderid,
|
||||
tradeid=packet['trade_id'],
|
||||
direction=DIRECTION_COINBASE2VT[packet['side']],
|
||||
price=packet['price'],
|
||||
volume=packet['size'],
|
||||
time=datetime.strptime(
|
||||
packet['time'], "%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
self.gateway.on_trade(trade)
|
||||
|
||||
|
||||
class OrderBook():
|
||||
"""
|
||||
Used to maintain orderbook of coinbase data
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, exchange: Exchange, gateway: BaseGateway):
|
||||
"""
|
||||
one symbol per orderbook
|
||||
"""
|
||||
|
||||
self.asks = dict()
|
||||
self.bids = dict()
|
||||
self.gateway = gateway
|
||||
self.newest_tick = TickData(
|
||||
"COINBASE", symbol, exchange, datetime.now())
|
||||
|
||||
def on_message(self, d: dict):
|
||||
"""
|
||||
callback by websocket when server send orderbook data
|
||||
"""
|
||||
if d['type'] == 'l2update':
|
||||
dt = datetime.strptime(
|
||||
d["time"][:-4] + d['time'][-1], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
self.on_update(d['changes'][0], dt)
|
||||
elif d['type'] == 'snapshot':
|
||||
self.on_snapshot(d['asks'], d['bids'])
|
||||
else:
|
||||
self.on_ticker(d)
|
||||
|
||||
def on_update(self, d: list, dt):
|
||||
"""
|
||||
call back when type is 12update
|
||||
"""
|
||||
size = d[2]
|
||||
price = d[1]
|
||||
side = d[0]
|
||||
|
||||
if side == 'buy':
|
||||
if float(price) in self.bids:
|
||||
if size == 0:
|
||||
del self.bids[float(price)]
|
||||
else:
|
||||
self.bids[float(price)] = float(size)
|
||||
else:
|
||||
self.bids[float(price)] = float(size)
|
||||
else:
|
||||
if float(price) in self.asks:
|
||||
if size == 0:
|
||||
del self.asks[float(price)]
|
||||
else:
|
||||
self.asks[float(price)] = float(size)
|
||||
else:
|
||||
self.asks[float(price)] = float(size)
|
||||
|
||||
self.generate_tick(dt)
|
||||
|
||||
def on_ticker(self, d: dict):
|
||||
"""
|
||||
call back when type is ticker
|
||||
"""
|
||||
tick = self.newest_tick
|
||||
|
||||
tick.openPrice = float(d['open_24h'])
|
||||
tick.highPrice = float(d['high_24h'])
|
||||
tick.lowPrice = float(d['low_24h'])
|
||||
tick.lastPrice = float(d['price'])
|
||||
tick.volume = float(d['volume_24h'])
|
||||
|
||||
dt = datetime.strptime(
|
||||
d['time'], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
|
||||
self.gateway.on_tick(copy(tick))
|
||||
|
||||
def on_snapshot(self, asks: Sequence[List], bids: Sequence[List]):
|
||||
"""
|
||||
call back when type is snapshot
|
||||
"""
|
||||
for price, size in asks:
|
||||
self.asks[float(price)] = float(size)
|
||||
|
||||
for price, size in bids:
|
||||
self.bids[float(price)] = float(size)
|
||||
|
||||
def generate_tick(self, dt: datetime):
|
||||
""""""
|
||||
tick = self.newest_tick
|
||||
|
||||
bids_keys = self.bids.keys()
|
||||
bids_keys = sorted(bids_keys, reverse=True)
|
||||
|
||||
tick.bid_price_1 = bids_keys[0]
|
||||
tick.bid_price_2 = bids_keys[1]
|
||||
tick.bid_price_3 = bids_keys[2]
|
||||
tick.bid_price_4 = bids_keys[3]
|
||||
tick.bid_price_5 = bids_keys[4]
|
||||
|
||||
tick.bid_volume_1 = self.bids[bids_keys[0]]
|
||||
tick.bid_volume_2 = self.bids[bids_keys[1]]
|
||||
tick.bid_volume_3 = self.bids[bids_keys[2]]
|
||||
tick.bid_volume_4 = self.bids[bids_keys[3]]
|
||||
tick.bid_volume_5 = self.bids[bids_keys[4]]
|
||||
|
||||
asks_keys = self.asks.keys()
|
||||
asks_keys = sorted(asks_keys)
|
||||
|
||||
tick.ask_price_1 = asks_keys[0]
|
||||
tick.ask_price_2 = asks_keys[1]
|
||||
tick.ask_price_3 = asks_keys[2]
|
||||
tick.ask_price_4 = asks_keys[3]
|
||||
tick.ask_price_5 = asks_keys[4]
|
||||
|
||||
tick.ask_volume_1 = self.asks[asks_keys[0]]
|
||||
tick.ask_volume_2 = self.asks[asks_keys[1]]
|
||||
tick.ask_volume_3 = self.asks[asks_keys[2]]
|
||||
tick.ask_volume_4 = self.asks[asks_keys[3]]
|
||||
tick.ask_volume_5 = self.asks[asks_keys[4]]
|
||||
|
||||
tick.datetime = dt
|
||||
self.gateway.on_tick(copy(tick))
|
||||
|
||||
|
||||
class CoinbaseRestApi(RestClient):
|
||||
"""
|
||||
Coinbase REST API
|
||||
"""
|
||||
|
||||
def __init__(self, gateway: BaseGateway):
|
||||
""""""
|
||||
super(CoinbaseRestApi, self).__init__()
|
||||
|
||||
self.gateway = gateway
|
||||
self.gateway_name = gateway.gateway_name
|
||||
|
||||
self.key = ""
|
||||
self.secret = ""
|
||||
self.passphrase = ""
|
||||
|
||||
self.order_count = 1_000_000
|
||||
self.order_count_lock = Lock()
|
||||
|
||||
self.connect_time = 0
|
||||
|
||||
self.accounts = {}
|
||||
|
||||
self.rate_limit = 5
|
||||
self.rate_limit_remaining = 5
|
||||
|
||||
def sign(self, request):
|
||||
"""
|
||||
Generate Coinbase signature
|
||||
"""
|
||||
timestamp = str(time.time())
|
||||
message = ''.join([timestamp, request.method,
|
||||
request.path, request.data or ""])
|
||||
request.headers = (get_auth_header(timestamp, message,
|
||||
self.key,
|
||||
self.secret,
|
||||
self.passphrase))
|
||||
return request
|
||||
|
||||
def connect(
|
||||
self,
|
||||
key: str,
|
||||
secret: str,
|
||||
passphrase: str,
|
||||
session_number: int,
|
||||
server: str,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
):
|
||||
"""
|
||||
Initialize connection to REST server
|
||||
"""
|
||||
self.key = key
|
||||
self.secret = secret.encode()
|
||||
self.passphrase = passphrase
|
||||
|
||||
self.connect_time = (
|
||||
int(datetime.now().strftime("%y%m%d%H%M%S")) * self.order_count
|
||||
)
|
||||
if server == "REAL":
|
||||
self.init(REST_HOST, proxy_host, proxy_port)
|
||||
else:
|
||||
self.init(SANDBOX_REST_HOST, proxy_host, proxy_port)
|
||||
|
||||
self.start(session_number)
|
||||
self.gateway.rest_api_inited = True
|
||||
|
||||
self.qry_instrument()
|
||||
# self.qry_orders()
|
||||
|
||||
self.gateway.write_log("REST API启动成功")
|
||||
|
||||
def qry_instrument(self):
|
||||
"""
|
||||
Get the instrument of Coinbase
|
||||
"""
|
||||
self.add_request(
|
||||
"GET",
|
||||
"/products",
|
||||
callback=self.on_qry_instrument,
|
||||
params={},
|
||||
on_error=self.on_qry_instrument_error,
|
||||
)
|
||||
|
||||
def qry_orders(self):
|
||||
""""""
|
||||
params = {"status": "all"}
|
||||
self.add_request(
|
||||
"GET",
|
||||
"/orders",
|
||||
callback=self.on_qry_orders,
|
||||
params=params,
|
||||
on_error=self.on_qry_orders_error,
|
||||
)
|
||||
|
||||
def qry_account(self):
|
||||
""""""
|
||||
self.add_request(
|
||||
"GET",
|
||||
"/accounts",
|
||||
callback=self.on_qry_account,
|
||||
params={},
|
||||
on_error=self.on_qry_account_error,
|
||||
)
|
||||
|
||||
def on_qry_account(self, data, request):
|
||||
""""""
|
||||
for acc in data:
|
||||
account_id = str(acc['id'])
|
||||
account = self.accounts.get(account_id, None)
|
||||
if not account:
|
||||
account = AccountData(accountid=account_id,
|
||||
gateway_name=self.gateway_name)
|
||||
self.accounts[account_id] = account
|
||||
|
||||
account.balance = acc.get("balance", account.balance)
|
||||
account.available = acc.get("available", account.available)
|
||||
account.frozen = acc.get("hold", account.frozen)
|
||||
|
||||
self.gateway.on_account(copy(account))
|
||||
|
||||
def on_qry_account_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request):
|
||||
""""""
|
||||
if not issubclass(exception_type, ConnectionError):
|
||||
self.on_error(exception_type, exception_value, tb, request)
|
||||
|
||||
def on_qry_orders_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request):
|
||||
""""""
|
||||
if not issubclass(exception_type, ConnectionError):
|
||||
self.on_error(exception_type, exception_value, tb, request)
|
||||
|
||||
def on_qry_orders(self, data, request):
|
||||
""""""
|
||||
for d in data:
|
||||
date, time = d['created_at'].split('T')
|
||||
if d['status'] == 'open':
|
||||
if not d['filled_size']:
|
||||
status = Status.NOTTRADED
|
||||
else:
|
||||
status = Status.PARTTRADED
|
||||
else:
|
||||
if d['size'] == d['filled_size']:
|
||||
status = Status.ALLTRADED
|
||||
else:
|
||||
status = Status.CANCELLED
|
||||
order = OrderData(
|
||||
symbol=d['product_id'],
|
||||
gateway_name=self.gateway_name,
|
||||
exchange=Exchange.COINBASE,
|
||||
orderid=d['id'],
|
||||
direction=DIRECTION_COINBASE2VT[d['side']],
|
||||
price=float(d['price']),
|
||||
volume=float(d['size']),
|
||||
traded=float(d['filled_size']),
|
||||
time=time.replace('Z', ""),
|
||||
status=status,
|
||||
)
|
||||
self.gateway.on_order(copy(order))
|
||||
|
||||
orderDict[order.orderid] = order
|
||||
orderSysDict[order.orderid] = order.orderID
|
||||
|
||||
self.gateway.writeLog(u'委托信息查询成功')
|
||||
|
||||
def on_qry_instrument_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request: Request):
|
||||
"""
|
||||
Callback when sending order caused exception.
|
||||
"""
|
||||
# Record exception if not ConnectionError
|
||||
if not issubclass(exception_type, ConnectionError):
|
||||
self.on_error(exception_type, exception_value, tb, request)
|
||||
|
||||
def on_qry_instrument(self, data, request):
|
||||
""""""
|
||||
for d in data:
|
||||
contract = ContractData(
|
||||
symbol=d['id'],
|
||||
exchange=Exchange.COINBASE,
|
||||
name=d['display_name'],
|
||||
product=Product.SPOT,
|
||||
pricetick=d['quote_increment'],
|
||||
size=d['base_min_size'],
|
||||
stop_supported=(not d['limit_only']),
|
||||
net_position=True,
|
||||
history_data=False,
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
|
||||
self.gateway.on_contract(contract)
|
||||
|
||||
self.gateway.product_id.append(d['id'])
|
||||
self.gateway.received_instrument = True
|
||||
|
||||
def send_order(self, req: OrderRequest):
|
||||
""""""
|
||||
if not self.check_rate_limit():
|
||||
return
|
||||
|
||||
orderid = str(uuid.uuid1())
|
||||
|
||||
data = {
|
||||
"size": req.volume,
|
||||
"product_id": req.symbol,
|
||||
"side": DIRECTION_VT2COINBASE[req.direction],
|
||||
"type": ORDERTYPE_VT2COINBASE[req.type],
|
||||
"client_oid": orderid,
|
||||
}
|
||||
|
||||
if req.type == OrderType.LIMIT:
|
||||
data['price'] = req.price
|
||||
elif req.type == OrderType.STOP:
|
||||
data['stop_price'] = req.price
|
||||
data['stop'] = STOP_VT2COINBASE[req.Direction]
|
||||
|
||||
order = req.create_order_data(orderid, self.gateway_name)
|
||||
self.add_request(
|
||||
"POST",
|
||||
"/orders",
|
||||
callback=self.on_send_order,
|
||||
data=json.dumps(data),
|
||||
params={},
|
||||
extra=order,
|
||||
on_failed=self.on_send_order_failed,
|
||||
on_error=self.on_send_order_error,
|
||||
)
|
||||
|
||||
return order.vt_orderid
|
||||
|
||||
def on_send_order_failed(self, status_code: str, request: Request):
|
||||
"""
|
||||
Callback when sending order failed on server.
|
||||
"""
|
||||
order = request.extra
|
||||
order.status = Status.REJECTED
|
||||
self.gateway.on_order(order)
|
||||
|
||||
if request.response.text:
|
||||
data = request.response.json()
|
||||
error = data["message"]
|
||||
msg = f"委托失败,状态码:{status_code},信息:{error}"
|
||||
else:
|
||||
msg = f"委托失败,状态码:{status_code}"
|
||||
|
||||
self.gateway.write_log(msg)
|
||||
|
||||
def on_send_order_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request: Request):
|
||||
"""
|
||||
Callback when sending order caused exception.
|
||||
"""
|
||||
|
||||
order = request.extra
|
||||
order.status = Status.REJECTED
|
||||
self.gateway.on_order(order)
|
||||
|
||||
# Record exception if not ConnectionError
|
||||
if not issubclass(exception_type, ConnectionError):
|
||||
self.on_error(exception_type, exception_value, tb, request)
|
||||
|
||||
def on_send_order(self, data, request):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def cancel_order(self, req: CancelRequest):
|
||||
""""""
|
||||
if not self.check_rate_limit():
|
||||
return
|
||||
|
||||
orderid = req.orderid
|
||||
|
||||
if orderid not in orderSysDict:
|
||||
cancelDict[orderid] = req
|
||||
|
||||
self.add_request(
|
||||
"DELETE",
|
||||
"/orders/" + orderid,
|
||||
callback=self.on_cancel_order,
|
||||
params={},
|
||||
on_error=self.on_cancel_order_error,
|
||||
on_failed=self.on_cancel_order_failed,
|
||||
)
|
||||
|
||||
def on_cancel_order_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request: Request):
|
||||
"""
|
||||
Callback when cancelling order failed on server.
|
||||
"""
|
||||
# Record exception if not ConnectionError
|
||||
if not issubclass(exception_type, ConnectionError):
|
||||
self.on_error(exception_type, exception_value, tb, request)
|
||||
|
||||
def on_cancel_order(self, data, request):
|
||||
"""Websocket will push a new order status"""
|
||||
pass
|
||||
|
||||
def on_cancel_order_failed(self, status_code: str, request: Request):
|
||||
"""
|
||||
Callback when sending order failed on server.
|
||||
"""
|
||||
if request.response.text:
|
||||
data = request.response.json()
|
||||
error = data["message"]
|
||||
msg = f"委托失败,状态码:{status_code},信息:{error}"
|
||||
else:
|
||||
msg = f"委托失败,状态码:{status_code}"
|
||||
|
||||
self.gateway.write_log(msg)
|
||||
|
||||
def on_failed(self, status_code: int, request: Request):
|
||||
"""
|
||||
Callback to handle request failed.
|
||||
"""
|
||||
data = request.response.json()
|
||||
error = data['message']
|
||||
msg = f"请求失败,状态码:{status_code},信息:{error}"
|
||||
self.gateway.write_log(msg)
|
||||
|
||||
def on_error(
|
||||
self,
|
||||
exception_type: type,
|
||||
exception_value: Exception,
|
||||
tb,
|
||||
request: Request):
|
||||
"""
|
||||
Callback to handler request exception.
|
||||
"""
|
||||
msg = f"触发异常,状态码:{exception_type},信息:{exception_value}"
|
||||
self.gateway.write_log(msg)
|
||||
|
||||
sys.stderr.write(
|
||||
self.exception_detail(exception_type, exception_value, tb, request)
|
||||
)
|
||||
|
||||
def reset_rate_limit(self):
|
||||
"""
|
||||
reset the rate limit every 1 sec
|
||||
"""
|
||||
self.rate_limit_remaining = 5
|
||||
|
||||
def check_rate_limit(self):
|
||||
"""
|
||||
Called before send requests
|
||||
"""
|
||||
if self.rate_limit_remaining:
|
||||
self.rate_limit_remaining -= 1
|
||||
return True
|
||||
else:
|
||||
self.gateway.write_log("已超出请求速率上限,请稍后重试")
|
||||
return False
|
||||
|
||||
|
||||
def get_auth_header(
|
||||
timestamp,
|
||||
message,
|
||||
api_key,
|
||||
secret_key,
|
||||
passphrase):
|
||||
""""""
|
||||
message = message.encode("ascii")
|
||||
hmac_key = base64.b64decode(secret_key)
|
||||
signature = hmac.new(hmac_key, message, hashlib.sha256)
|
||||
signature_b64 = base64.b64encode(signature.digest()).decode('utf-8')
|
||||
return{
|
||||
'Content-Type': 'Application/JSON',
|
||||
'CB-ACCESS-SIGN': signature_b64,
|
||||
'CB-ACCESS-TIMESTAMP': timestamp,
|
||||
'CB-ACCESS-KEY': api_key,
|
||||
'CB-ACCESS-PASSPHRASE': passphrase
|
||||
}
|
Loading…
Reference in New Issue
Block a user