From 4cdf809d41d12d06e57840c02ec61bc10ef65bb5 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 9 May 2019 21:33:09 +0800 Subject: [PATCH 1/5] [Add] send_orders and cancel_orders function --- vnpy/trader/gateway.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vnpy/trader/gateway.py b/vnpy/trader/gateway.py index ac2cd704..b7af2f59 100644 --- a/vnpy/trader/gateway.py +++ b/vnpy/trader/gateway.py @@ -3,7 +3,7 @@ """ from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Sequence from copy import copy from vnpy.event import Event, EventEngine @@ -210,6 +210,29 @@ class BaseGateway(ABC): """ pass + def send_orders(self, reqs: Sequence[OrderRequest]): + """ + Send a batch of orders to server. + Use a for loop of send_order function by default. + Reimplement this function if batch order supported on server. + """ + vt_orderids = [] + + for req in reqs: + vt_orderid = self.send_order(req) + vt_orderids.append(vt_orderid) + + return vt_orderids + + def cancel_orders(self, reqs: Sequence[CancelRequest]): + """ + Cancel a batch of orders to server. + Use a for loop of cancel_order function by default. + Reimplement this function if batch cancel supported on server. + """ + for req in reqs: + self.cancel_order(req) + @abstractmethod def query_account(self): """ From 6a64313fbf97440f6f31a4da30e163ef63852591 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 10 May 2019 13:41:09 +0800 Subject: [PATCH 2/5] [Add] support batch order in HbdmGateway --- vnpy/gateway/hbdm/hbdm_gateway.py | 110 ++++++++++++++++++++++++++++-- vnpy/trader/engine.py | 18 ++++- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/vnpy/gateway/hbdm/hbdm_gateway.py b/vnpy/gateway/hbdm/hbdm_gateway.py index de681d24..022f6842 100644 --- a/vnpy/gateway/hbdm/hbdm_gateway.py +++ b/vnpy/gateway/hbdm/hbdm_gateway.py @@ -12,6 +12,7 @@ import hmac from copy import copy from datetime import datetime from threading import Lock +from typing import Sequence from vnpy.event import Event from vnpy.api.rest import RestClient, Request @@ -129,6 +130,10 @@ class HbdmGateway(BaseGateway): """""" self.rest_api.cancel_order(req) + def send_orders(self, reqs: Sequence[OrderRequest]): + """""" + return self.rest_api.send_orders(reqs) + def query_account(self): """""" self.rest_api.query_account() @@ -288,8 +293,8 @@ class HbdmRestApi(RestClient): "client_order_id": int(local_orderid), "price": req.price, "volume": int(req.volume), - "direction": DIRECTION_VT2HBDM[req.direction], - "offset": OFFSET_VT2HBDM[req.offset], + "direction": DIRECTION_VT2HBDM.get(req.direction, ""), + "offset": OFFSET_VT2HBDM.get(req.offset, ""), "order_price_type": ORDERTYPE_VT2HBDM.get(req.type, ""), "lever_rate": 20 } @@ -307,6 +312,53 @@ class HbdmRestApi(RestClient): self.gateway.on_order(order) return order.vt_orderid + def send_orders(self, reqs: Sequence[OrderRequest]): + """""" + orders_data = [] + orders = [] + vt_orderids = [] + + for req in reqs: + local_orderid = self.new_local_orderid() + + order = req.create_order_data( + local_orderid, + self.gateway_name + ) + order.time = datetime.now().strftime("%H:%M:%S") + self.gateway.on_order(order) + + d = { + "contract_code": req.symbol, + "client_order_id": int(local_orderid), + "price": req.price, + "volume": int(req.volume), + "direction": DIRECTION_VT2HBDM.get(req.direction, ""), + "offset": OFFSET_VT2HBDM.get(req.offset, ""), + "order_price_type": ORDERTYPE_VT2HBDM.get(req.type, ""), + "lever_rate": 20 + } + + orders_data.append(d) + orders.append(order) + vt_orderids.append(order.vt_orderid) + + data = { + "orders_data": orders_data + } + + self.add_request( + method="POST", + path="/api/v1/contract_batchorder", + callback=self.on_send_orders, + data=data, + extra=orders, + on_error=self.on_send_orders_error, + on_failed=self.on_send_orders_failed + ) + + return vt_orderids + def cancel_order(self, req: CancelRequest): """""" buf = [i for i in req.symbol if not i.isdigit()] @@ -315,10 +367,11 @@ class HbdmRestApi(RestClient): "symbol": "".join(buf), } - if req.orderid > 1000000: - data["client_order_id"] = int(req.orderid) + orderid = int(req.orderid) + if orderid > 1000000: + data["client_order_id"] = orderid else: - data["order_id"] = int(req.orderid) + data["order_id"] = orderid self.add_request( method="POST", @@ -479,6 +532,53 @@ class HbdmRestApi(RestClient): msg = f"撤单失败,状态码:{status_code},信息:{request.response.text}" self.gateway.write_log(msg) + def on_send_orders(self, data, request): + """""" + orders = request.extra + + errors = data.get("errors", None) + if errors: + for d in errors: + ix = d["index"] + code = d["err_code"] + msg = d["err_msg"] + + order = orders[ix] + order.status = Status.REJECTED + self.gateway.on_order(order) + + msg = f"批量委托失败,状态码:{code},信息:{msg}" + self.gateway.write_log(msg) + + def on_send_orders_failed(self, status_code: str, request: Request): + """ + Callback when sending order failed on server. + """ + orders = request.extra + + for order in orders: + order.status = Status.REJECTED + self.gateway.on_order(order) + + msg = f"批量委托失败,状态码:{status_code},信息:{request.response.text}" + self.gateway.write_log(msg) + + def on_send_orders_error( + self, exception_type: type, exception_value: Exception, tb, request: Request + ): + """ + Callback when sending order caused exception. + """ + orders = request.extra + + for order in orders: + 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 check_error(self, data: dict, func: str = ""): """""" if data["status"] != "error": diff --git a/vnpy/trader/engine.py b/vnpy/trader/engine.py index 96246487..9b60bae0 100644 --- a/vnpy/trader/engine.py +++ b/vnpy/trader/engine.py @@ -8,7 +8,7 @@ from datetime import datetime from email.message import EmailMessage from queue import Empty, Queue from threading import Thread -from typing import Any +from typing import Any, Sequence from vnpy.event import Event, EventEngine from .app import BaseApp @@ -180,6 +180,22 @@ class MainEngine: if gateway: gateway.cancel_order(req) + def send_orders(self, reqs: Sequence[OrderRequest], gateway_name: str): + """ + """ + gateway = self.get_gateway(gateway_name) + if gateway: + return gateway.send_orders(reqs) + else: + return ["" for req in reqs] + + def cancel_orders(self, reqs: Sequence[CancelRequest], gateway_name: str): + """ + """ + gateway = self.get_gateway(gateway_name) + if gateway: + gateway.cancel_orders(reqs) + def query_history(self, req: HistoryRequest, gateway_name: str): """ Send cancel order request to a specific gateway. From 3b279c68eb0b061e1e27d19ad0408fa212d3fefb Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 10 May 2019 14:06:27 +0800 Subject: [PATCH 3/5] [Add] query history order and trade data --- vnpy/gateway/hbdm/hbdm_gateway.py | 115 +++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 11 deletions(-) diff --git a/vnpy/gateway/hbdm/hbdm_gateway.py b/vnpy/gateway/hbdm/hbdm_gateway.py index 022f6842..e6df8b20 100644 --- a/vnpy/gateway/hbdm/hbdm_gateway.py +++ b/vnpy/gateway/hbdm/hbdm_gateway.py @@ -57,7 +57,8 @@ ORDERTYPE_VT2HBDM = { OrderType.LIMIT: "limit", } ORDERTYPE_HBDM2VT = {v: k for k, v in ORDERTYPE_VT2HBDM.items()} - +ORDERTYPE_HBDM2VT[1] = OrderType.LIMIT +ORDERTYPE_HBDM2VT[3] = OrderType.MARKET DIRECTION_VT2HBDM = { Direction.LONG: "buy", @@ -174,7 +175,7 @@ class HbdmRestApi(RestClient): self.gateway = gateway self.gateway_name = gateway.gateway_name - + self.host = "" self.key = "" self.secret = "" @@ -254,12 +255,47 @@ class HbdmRestApi(RestClient): def query_order(self): """""" for currency in self.currencies: + # Open Orders data = {"symbol": currency} self.add_request( method="POST", path="/api/v1/contract_openorders", - callback=self.on_query_order, + callback=self.on_query_active_order, + data=data, + extra=currency + ) + + # History Orders + data = { + "symbol": currency, + "trade_type": 0, + "type": 2, + "status": 0, + "create_date": 7 + } + + self.add_request( + method="POST", + path="/api/v1/contract_hisorders", + callback=self.on_query_history_order, + data=data, + extra=currency + ) + + def query_trade(self): + """""" + for currency in self.currencies: + data = { + "symbol": currency, + "trade_type": 0, + "create_date": 7 + } + + self.add_request( + method="POST", + path="/api/v1/contract_matchresults", + callback=self.on_query_trade, data=data, extra=currency ) @@ -271,7 +307,7 @@ class HbdmRestApi(RestClient): path="/api/v1/contract_contract_info", callback=self.on_query_contract ) - + def new_local_orderid(self): """""" with self.order_count_lock: @@ -320,7 +356,7 @@ class HbdmRestApi(RestClient): for req in reqs: local_orderid = self.new_local_orderid() - + order = req.create_order_data( local_orderid, self.gateway_name @@ -412,7 +448,7 @@ class HbdmRestApi(RestClient): for d in data["data"]: key = f"{d['contract_code']}_{d['direction']}" position = self.positions.get(key, None) - + if not position: position = PositionData( symbol=d["contract_code"], @@ -426,17 +462,18 @@ class HbdmRestApi(RestClient): position.frozen = d["frozen"] position.price = d["cost_hold"] position.pnl = d["profit"] - + for position in self.positions.values(): self.gateway.on_position(position) - def on_query_order(self, data, request): + def on_query_active_order(self, data, request): """""" - if self.check_error(data, "查询委托"): + if self.check_error(data, "查询活动委托"): return for d in data["data"]["orders"]: - dt = datetime.fromtimestamp(d["created_at"] / 1000) + timestamp = d["created_at"] + dt = datetime.fromtimestamp(timestamp / 1000) time = dt.strftime("%H:%M:%S") if d["client_order_id"]: @@ -460,7 +497,62 @@ class HbdmRestApi(RestClient): ) self.gateway.on_order(order) - self.gateway.write_log(f"{request.extra}委托信息查询成功") + self.gateway.write_log(f"{request.extra}活动委托信息查询成功") + + def on_query_history_order(self, data, request): + """""" + if self.check_error(data, "查询历史委托"): + return + + for d in data["data"]["orders"]: + timestamp = d["create_date"] + dt = datetime.fromtimestamp(timestamp / 1000) + time = dt.strftime("%H:%M:%S") + + orderid = d["order_id"] + + order = OrderData( + orderid=orderid, + symbol=d["contract_code"], + exchange=Exchange.HUOBI, + price=d["price"], + volume=d["volume"], + type=ORDERTYPE_HBDM2VT[d["order_price_type"]], + direction=DIRECTION_HBDM2VT[d["direction"]], + offset=OFFSET_HBDM2VT[d["offset"]], + traded=d["trade_volume"], + status=STATUS_HBDM2VT[d["status"]], + time=time, + gateway_name=self.gateway_name, + ) + self.gateway.on_order(order) + + self.gateway.write_log(f"{request.extra}历史委托信息查询成功") + + def on_query_trade(self, data, request): + """""" + if self.check_error(data, "查询成交"): + return + + for d in data["data"]["trades"]: + dt = datetime.fromtimestamp(d["create_date"] / 1000) + time = dt.strftime("%H:%M:%S") + + trade = TradeData( + tradeid=d["match_id"], + orderid=d["order_id"], + symbol=d["contract_code"], + exchange=Exchange.HUOBI, + price=d["trade_price"], + volume=d["trade_volume"], + direction=DIRECTION_HBDM2VT[d["direction"]], + offset=OFFSET_HBDM2VT[d["offset"]], + time=time, + gateway_name=self.gateway_name, + ) + self.gateway.on_trade(trade) + + self.gateway.write_log(f"{request.extra}成交信息查询成功") def on_query_contract(self, data, request): # type: (dict, Request)->None """""" @@ -487,6 +579,7 @@ class HbdmRestApi(RestClient): self.gateway.write_log("合约信息查询成功") self.query_order() + self.query_trade() def on_send_order(self, data, request): """""" From 04906bf277d7def1c1110435856f0c7e5edb56a1 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 10 May 2019 14:29:04 +0800 Subject: [PATCH 4/5] [Add] query history data function --- vnpy/app/cta_backtester/ui/widget.py | 3 + vnpy/gateway/hbdm/hbdm_gateway.py | 91 +++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/vnpy/app/cta_backtester/ui/widget.py b/vnpy/app/cta_backtester/ui/widget.py index 2198c11d..04cb7349 100644 --- a/vnpy/app/cta_backtester/ui/widget.py +++ b/vnpy/app/cta_backtester/ui/widget.py @@ -521,6 +521,9 @@ class BacktesterChart(pg.GraphicsWindow): def set_data(self, df): """""" + if df is None: + return + count = len(df) self.dates.clear() diff --git a/vnpy/gateway/hbdm/hbdm_gateway.py b/vnpy/gateway/hbdm/hbdm_gateway.py index e6df8b20..b26629b1 100644 --- a/vnpy/gateway/hbdm/hbdm_gateway.py +++ b/vnpy/gateway/hbdm/hbdm_gateway.py @@ -23,19 +23,22 @@ from vnpy.trader.constant import ( Exchange, Product, Status, - OrderType + OrderType, + Interval ) from vnpy.trader.gateway import BaseGateway from vnpy.trader.object import ( TickData, OrderData, TradeData, + BarData, AccountData, PositionData, ContractData, OrderRequest, CancelRequest, - SubscribeRequest + SubscribeRequest, + HistoryRequest ) from vnpy.trader.event import EVENT_TIMER @@ -72,6 +75,18 @@ OFFSET_VT2HBDM = { } OFFSET_HBDM2VT = {v: k for k, v in OFFSET_VT2HBDM.items()} +INTERVAL_VT2HBDM = { + Interval.MINUTE: "1min", + Interval.HOUR: "60min", + Interval.DAILY: "1day" +} + +CONTRACT_TYPE_MAP = { + "this_week": "CW", + "next_week": "NW", + "this_quarter": "CQ" +} + symbol_type_map = {} @@ -143,6 +158,10 @@ class HbdmGateway(BaseGateway): """""" self.rest_api.query_position() + def query_history(self, req: HistoryRequest): + """""" + return self.rest_api.query_history(req) + def close(self): """""" self.rest_api.stop() @@ -308,6 +327,66 @@ class HbdmRestApi(RestClient): callback=self.on_query_contract ) + def query_history(self, req: HistoryRequest): + """""" + # Convert symbol + contract_type = symbol_type_map.get(req.symbol, "") + buf = [i for i in req.symbol if not i.isdigit()] + symbol = "".join(buf) + + ws_contract_type = CONTRACT_TYPE_MAP[contract_type] + ws_symbol = f"{symbol}_{ws_contract_type}" + + # Create query params + params = { + "symbol": ws_symbol, + "period": INTERVAL_VT2HBDM[req.interval], + "size": 2000 + } + + # Get response from server + resp = self.request( + "GET", + "/market/history/kline", + params=params + ) + + # Break if request failed with other status code + history = [] + + if resp.status_code // 100 != 2: + msg = f"获取历史数据失败,状态码:{resp.status_code},信息:{resp.text}" + self.gateway.write_log(msg) + else: + data = resp.json() + if not data: + msg = f"获取历史数据为空" + self.gateway.write_log(msg) + else: + for d in data["data"]: + dt = datetime.fromtimestamp(d["id"]) + + bar = BarData( + symbol=req.symbol, + exchange=req.exchange, + datetime=dt, + interval=req.interval, + volume=d["vol"], + open_price=d["open"], + high_price=d["high"], + low_price=d["low"], + close_price=d["close"], + gateway_name=self.gateway_name + ) + history.append(bar) + + begin = history[0].datetime + end = history[-1].datetime + msg = f"获取历史数据成功,{req.symbol} - {req.interval.value},{begin} - {end}" + self.gateway.write_log(msg) + + return history + def new_local_orderid(self): """""" with self.order_count_lock: @@ -570,6 +649,7 @@ class HbdmRestApi(RestClient): size=int(d["contract_size"]), min_volume=1, product=Product.FUTURES, + history_data=True, gateway_name=self.gateway_name, ) self.gateway.on_contract(contract) @@ -863,11 +943,6 @@ class HbdmTradeWebsocketApi(HbdmWebsocketApiBase): class HbdmDataWebsocketApi(HbdmWebsocketApiBase): """""" - CONTRACT_TYPE_MAP = { - "this_week": "CW", - "next_week": "NW", - "this_quarter": "CQ" - } def __init__(self, gateway): """""" @@ -892,7 +967,7 @@ class HbdmDataWebsocketApi(HbdmWebsocketApiBase): buf = [i for i in req.symbol if not i.isdigit()] symbol = "".join(buf) - ws_contract_type = self.CONTRACT_TYPE_MAP[contract_type] + ws_contract_type = CONTRACT_TYPE_MAP[contract_type] ws_symbol = f"{symbol}_{ws_contract_type}" # Create tick data buffer From 5b120f27cdfc239fe1693666d7d89ae4de62cf8e Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 10 May 2019 14:30:45 +0800 Subject: [PATCH 5/5] [Add] deap module into setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5d2e6575..4acb9fb8 100644 --- a/setup.py +++ b/setup.py @@ -124,7 +124,8 @@ install_requires = [ "tigeropen", "rqdatac", "ta-lib", - "ibapi" + "ibapi", + "deap" ] if sys.version_info.minor < 7: install_requires.append("dataclasses")