From b56d0ce160d307ce9ee4a18b8791099971e313a0 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 21 May 2019 14:26:55 +0800 Subject: [PATCH] [Add] XtpGateway support margin trading --- tests/trader/run.py | 26 +++++----- vnpy/gateway/xtp/xtp_gateway.py | 86 ++++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/tests/trader/run.py b/tests/trader/run.py index b06b4af4..610e6f7d 100644 --- a/tests/trader/run.py +++ b/tests/trader/run.py @@ -35,20 +35,20 @@ def main(): main_engine = MainEngine(event_engine) main_engine.add_gateway(XtpGateway) - main_engine.add_gateway(CtpGateway) + # main_engine.add_gateway(CtpGateway) # main_engine.add_gateway(CtptestGateway) - main_engine.add_gateway(FemasGateway) - main_engine.add_gateway(IbGateway) - main_engine.add_gateway(FutuGateway) - main_engine.add_gateway(BitmexGateway) - main_engine.add_gateway(TigerGateway) - main_engine.add_gateway(OesGateway) - main_engine.add_gateway(OkexGateway) - main_engine.add_gateway(HuobiGateway) - main_engine.add_gateway(BitfinexGateway) - main_engine.add_gateway(OnetokenGateway) - main_engine.add_gateway(OkexfGateway) - main_engine.add_gateway(HbdmGateway) + # main_engine.add_gateway(FemasGateway) + # main_engine.add_gateway(IbGateway) + # main_engine.add_gateway(FutuGateway) + # main_engine.add_gateway(BitmexGateway) + # main_engine.add_gateway(TigerGateway) + # main_engine.add_gateway(OesGateway) + # main_engine.add_gateway(OkexGateway) + # main_engine.add_gateway(HuobiGateway) + # main_engine.add_gateway(BitfinexGateway) + # main_engine.add_gateway(OnetokenGateway) + # main_engine.add_gateway(OkexfGateway) + # main_engine.add_gateway(HbdmGateway) main_engine.add_app(CtaStrategyApp) main_engine.add_app(CtaBacktesterApp) diff --git a/vnpy/gateway/xtp/xtp_gateway.py b/vnpy/gateway/xtp/xtp_gateway.py index 0d8d784d..fbaff25b 100644 --- a/vnpy/gateway/xtp/xtp_gateway.py +++ b/vnpy/gateway/xtp/xtp_gateway.py @@ -17,6 +17,7 @@ from vnpy.api.xtp.vnxtp import ( XTPOrderInfo, XTPTradeReport, XTPOrderCancelInfo, + XTPCrdDebtInfo, XTPQueryStkPositionRsp, XTPQueryAssetRsp, XTPStructuredFundInfo, @@ -32,6 +33,11 @@ from vnpy.api.xtp.vnxtp import ( XTP_TE_RESUME_TYPE, XTP_SIDE_BUY, XTP_SIDE_SELL, + XTP_SIDE_MARGIN_TRADE, + XTP_SIDE_SHORT_SELL, + XTP_SIDE_REPAY_MARGIN, + XTP_SIDE_REPAY_STOCK, + XTP_ACCOUNT_TYPE, XTP_BUSINESS_TYPE, XTP_TICKER_TYPE, XTP_MARKET_TYPE, @@ -40,7 +46,7 @@ from vnpy.api.xtp.vnxtp import ( ) from vnpy.event import EventEngine from vnpy.trader.event import EVENT_TIMER -from vnpy.trader.constant import Exchange, Product, Direction, OrderType, Status +from vnpy.trader.constant import Exchange, Product, Direction, OrderType, Status, Offset from vnpy.trader.gateway import BaseGateway from vnpy.trader.object import (CancelRequest, OrderRequest, SubscribeRequest, TickData, ContractData, OrderData, TradeData, @@ -71,9 +77,17 @@ PRODUCT_XTP2VT = { XTP_TICKER_TYPE.XTP_TICKER_TYPE_OPTION: Product.OPTION } +# DIRECTION_VT2XTP = { +# Direction.LONG: XTP_SIDE_BUY, +# Direction.SHORT: XTP_SIDE_SELL +# } DIRECTION_VT2XTP = { - Direction.LONG: XTP_SIDE_BUY, - Direction.SHORT: XTP_SIDE_SELL + (Direction.LONG, Offset.OPEN): XTP_SIDE_MARGIN_TRADE, + (Direction.SHORT, Offset.CLOSE): XTP_SIDE_REPAY_MARGIN, + (Direction.SHORT, Offset.OPEN): XTP_SIDE_SHORT_SELL, + (Direction.LONG, Offset.CLOSE): XTP_SIDE_REPAY_STOCK, + (Direction.SHORT, Offset.NONE): XTP_SIDE_BUY, + (Direction.LONG, Offset.NONE): XTP_SIDE_SELL, } DIRECTION_XTP2VT = {v: k for k, v in DIRECTION_VT2XTP.items()} @@ -95,6 +109,7 @@ STATUS_XTP2VT = { symbol_name_map = {} +symbol_exchange_map = {} class XtpGateway(BaseGateway): @@ -428,6 +443,9 @@ class XtpQuoteApi(API.QuoteSpi): symbol_name_map[contract.vt_symbol] = contract.name + if contract.product != Product.INDEX: + symbol_exchange_map[contract.symbol] = contract.exchange + if is_last: self.gateway.write_log(f"{contract.exchange.value}合约信息查询成功") @@ -487,6 +505,13 @@ class XtpTraderApi(API.TraderSpi): self.session_id = 0 self.reqid = 0 + # Whether current account supports margin or option + self.margin_trading = False + self.option_trading = False + + # + self.short_positions = {} + def connect( self, userid: str, @@ -564,9 +589,13 @@ class XtpTraderApi(API.TraderSpi): xtp_req.market = MARKET_VT2XTP[req.exchange] xtp_req.price = req.price xtp_req.quantity = int(req.volume) - xtp_req.side = DIRECTION_VT2XTP[req.direction] + xtp_req.side = DIRECTION_VT2XTP.get((req.direction, req.offset), "") xtp_req.price_type = ORDERTYPE_VT2XTP[req.type] - xtp_req.business_type = XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_CASH + + if req.offset == Offset.NONE: + xtp_req.business_type = XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_CASH + else: + xtp_req.business_type = XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_MARGIN orderid = self.api.InsertOrder(xtp_req, self.session_id) @@ -595,6 +624,10 @@ class XtpTraderApi(API.TraderSpi): self.reqid += 1 self.api.QueryPosition("", self.session_id, self.reqid) + if self.margin_trading: + self.reqid += 1 + self.api.QueryCreditDebtInfo(self.session_id, self.reqid) + def check_error(self, func_name: str, error_info: XTPRspInfoStruct): """""" if error_info and error_info.error_id: @@ -617,12 +650,15 @@ class XtpTraderApi(API.TraderSpi): """""" self.check_error("委托下单", error_info) + direction, offset = DIRECTION_XTP2VT[ order_info.side] + order = OrderData( symbol=order_info.ticker, exchange=MARKET_XTP2VT[order_info.market], orderid=str(order_info.order_xtp_id), type=ORDERTYPE_XTP2VT[order_info.price_type], - direction=DIRECTION_XTP2VT[order_info.side], + direction=direction, + offset=offset, price=order_info.price, volume=order_info.quantity, traded=order_info.qty_traded, @@ -635,12 +671,15 @@ class XtpTraderApi(API.TraderSpi): def OnTradeEvent(self, trade_info: XTPTradeReport, session_id: int) -> Any: """""" + direction, offset = DIRECTION_XTP2VT[trade_info.side] + trade = TradeData( symbol=trade_info.ticker, exchange=MARKET_XTP2VT[trade_info.market], orderid=str(trade_info.order_xtp_id), tradeid=str(trade_info.exec_id), - direction=DIRECTION_XTP2VT[trade_info.side], + direction=direction, + offset=offset, price=trade_info.price, volume=trade_info.quantity, time=trade_info.trade_time, @@ -682,7 +721,7 @@ class XtpTraderApi(API.TraderSpi): position = PositionData( symbol=xtp_position.ticker, exchange=MARKET_XTP2VT[xtp_position.market], - direction=Direction.NET, + direction=Direction.LONG, volume=xtp_position.total_qty, frozen=xtp_position.locked_position, price=xtp_position.avg_price, @@ -703,6 +742,11 @@ class XtpTraderApi(API.TraderSpi): ) self.gateway.on_account(account) + if asset.account_type == XTP_ACCOUNT_TYPE.XTP_ACCOUNT_CREDIT: + self.margin_trading = True + elif asset.account_type == XTP_ACCOUNT_TYPE.XTP_ACCOUNT_DERIVE: + self.option_trading = True + def OnQueryStructuredFund(self, fund_info: XTPStructuredFundInfo, error_info: XTPRspInfoStruct, is_last: bool, session_id: int) -> Any: """""" @@ -741,3 +785,29 @@ class XtpTraderApi(API.TraderSpi): is_last: bool, session_id: int) -> Any: """""" pass + + def OnQueryCreditDebtInfo(self, debt_info: XTPCrdDebtInfo, error_info: XTPRspInfoStruct, + request_id: int, is_last: bool, session_id: int) -> Any: + """""" + if debt_info.debt_type == 1: + symbol = debt_info.ticker + exchange = MARKET_XTP2VT[debt_info.market] + + position = self.short_positions.get(symbol, None) + if not position: + position = PositionData( + symbol=symbol, + exchange=exchange, + direction=Direction.SHORT, + gateway_name=self.gateway_name + ) + self.short_positions[symbol] = position + + position.volume += debt_info.remain_qty + + if is_last: + for position in self.short_positions.values(): + self.gateway.on_position(position) + + self.short_positions.clear() +