VeighNa量化社区
你的开源社区量化交易平台
Member
avatar
加入于:
帖子: 44
声望: 2

之前在用2.9版本的脚本策略scripttrader交易的时候,遇到了一个平今转换上的问题,即不会自动把Offset.CLOSE转换成Offset.CLOSETODAY或Offset.CLOSEYESTERDAY,最终导致被拒单。当然可以为每个策略自行做本地仓位记录,记录今仓和昨仓的量,然后在下单时直接指定平今平昨,但是更新起来会比较麻烦,不如调用vnpy\trader\converter.py来实现自动转换。一步一步排查之后,我发现了问题所在,斗胆在下面展示一下我的解决方案。

首先来看vnpy_scripttrader\engine.py里面的send_order()函数:

    def send_order(
        self,
        vt_symbol: str,
        price: float,
        volume: float,
        direction: Direction,
        offset: Offset,
        order_type: OrderType
    ) -> str:
        """"""
        contract: Optional[ContractData] = self.get_contract(vt_symbol)
        if not contract:
            return ""

        req: OrderRequest = OrderRequest(
            symbol=contract.symbol,
            exchange=contract.exchange,
            direction=direction,
            type=order_type,
            volume=volume,
            price=price,
            offset=offset,
            reference=APP_NAME
        )

        vt_orderid: str = self.main_engine.send_order(req, contract.gateway_name)
        return vt_orderid

这里在使用sell()或者cover()这两个平仓的函数时,他传入的Offset对象是Offset.CLOSE,这里没有对平今平昨进行转换,而传Offset.CLOSE默认是平昨,所以在没有昨仓时会被拒单。那么我们需要在这一步用trader\converter.py中的OffsetConverter.convert_order_request()进行转换,但是似乎脚本策略中初始化一个OffsetConverter对象后直接调用并不能做到平今转换,还需要对OffsetConverter以及PositionHolding进行修改。原始代码如下

class OffsetConverter:
    """"""

    def __init__(self, main_engine: MainEngine):
        """"""
        self.main_engine: MainEngine = main_engine
        self.holdings: Dict[str, "PositionHolding"] = {}

    def update_position(self, position: PositionData) -> None:
        """"""
        if not self.is_convert_required(position.vt_symbol):
            return

        holding = self.get_position_holding(position.vt_symbol)
        holding.update_position(position)

    def update_trade(self, trade: TradeData) -> None:
        """"""
        if not self.is_convert_required(trade.vt_symbol):
            return

        holding = self.get_position_holding(trade.vt_symbol)
        holding.update_trade(trade)

    def update_order(self, order: OrderData) -> None:
        """"""
        if not self.is_convert_required(order.vt_symbol):
            return

        holding = self.get_position_holding(order.vt_symbol)
        holding.update_order(order)

    def update_order_request(self, req: OrderRequest, vt_orderid: str) -> None:
        """"""
        if not self.is_convert_required(req.vt_symbol):
            return

        holding = self.get_position_holding(req.vt_symbol)
        holding.update_order_request(req, vt_orderid)

    def get_position_holding(self, vt_symbol: str) -> "PositionHolding":
        """"""
        holding = self.holdings.get(vt_symbol, None)
        if not holding:
            contract = self.main_engine.get_contract(vt_symbol)
            holding = PositionHolding(contract)
            self.holdings[vt_symbol] = holding
        return holding

    def convert_order_request(
        self,
        req: OrderRequest,
        lock: bool,
        net: bool = False
    ) -> List[OrderRequest]:
        """"""
        if not self.is_convert_required(req.vt_symbol):
            return [req]

        holding = self.get_position_holding(req.vt_symbol)

        if lock:
            return holding.convert_order_request_lock(req)
        elif net:
            return holding.convert_order_request_net(req)
        elif req.exchange in [Exchange.SHFE, Exchange.INE]:
            return holding.convert_order_request_shfe(req)
        else:
            return [req]

class PositionHolding:
    """"""

    def __init__(self, contract: ContractData):
        """"""
        self.vt_symbol: str = contract.vt_symbol
        self.exchange: Exchange = contract.exchange

        self.active_orders: Dict[str, OrderData] = {}

        self.long_pos: float = 0
        self.long_yd: float = 0
        self.long_td: float = 0

        self.short_pos: float = 0
        self.short_yd: float = 0
        self.short_td: float = 0

        self.long_pos_frozen: float = 0
        self.long_yd_frozen: float = 0
        self.long_td_frozen: float = 0

        self.short_pos_frozen: float = 0
        self.short_yd_frozen: float = 0
        self.short_td_frozen: float = 0
    .......
    def convert_order_request_shfe(self, req: OrderRequest) -> List[OrderRequest]:
        """"""
        if req.offset == Offset.OPEN:
            return [req]

        if req.direction == Direction.LONG:
            pos_available = self.short_pos - self.short_pos_frozen
            td_available = self.short_td - self.short_td_frozen
        else:
            pos_available = self.long_pos - self.long_pos_frozen
            td_available = self.long_td - self.long_td_frozen

        if req.volume > pos_available:
            return []
        elif req.volume <= td_available:
            req_td = copy(req)
            req_td.offset = Offset.CLOSETODAY
            return [req_td]
        else:
            req_list = []

            if td_available > 0:
                req_td = copy(req)
                req_td.offset = Offset.CLOSETODAY
                req_td.volume = td_available
                req_list.append(req_td)

            req_yd = copy(req)
            req_yd.offset = Offset.CLOSEYESTERDAY
            req_yd.volume = req.volume - td_available
            req_list.append(req_yd)

            return req_list
    ......

不知道是什么原因,这个类中的holdings字典并不会在有交易时进行更新,所以get_position_holding(req.vt_symbol)得到的是初始值,以上期所合约订单转换为例,在调用convert_order_request_shfe(req)后,pos_available=0,返回空列表[]。为了解决这个问题,我们需要在convert_order_request()中执行holding=self.get_position_holding(req.vt_symbol)之前手动update_position()更新一下holdings字典。我修改后的代码如下:

    def convert_order_request(
        self,
        req: OrderRequest,
        lock: bool,
        net: bool = False
    ) -> List[OrderRequest]:
        """"""
        if not self.is_convert_required(req.vt_symbol):
            return [req]

        pos_long = self.main_engine.get_position(req.vt_symbol+'.多')
        pos_short = self.main_engine.get_position(req.vt_symbol+'.空')
        if pos_long:
            self.update_position(pos_long)
        if pos_short:
            self.update_position(pos_short)

        holding = self.get_position_holding(req.vt_symbol)

        if lock:
            return holding.convert_order_request_lock(req)
        elif net:
            return holding.convert_order_request_net(req)
        elif req.exchange in [Exchange.SHFE, Exchange.INE]:
            return holding.convert_order_request_shfe(req)
        else:
            return [req]

这样就可以正确执行convert_order_request_shfe(req)了,我们再回到vnpy_scripttrader\engine.py中来加入一步平今转换,我的做法是在send_order()中初始化一个converter,但似乎直接在engine初始化时就加入一个converter会更好(为了实盘稳定我没有尝试)。修改之后的send_order()如下:

    def send_order(
        self,
        vt_symbol: str,
        price: float,
        volume: float,
        direction: Direction,
        offset: Offset,
        order_type: OrderType
    ) -> str:
        """"""
        contract: Optional[ContractData] = self.get_contract(vt_symbol)
        if not contract:
            return ""

        req: OrderRequest = OrderRequest(
            symbol=contract.symbol,
            exchange=contract.exchange,
            direction=direction,
            type=order_type,
            volume=volume,
            price=price,
            offset=offset,
            reference=APP_NAME
            )
        try:
            converter=OffsetConverter(self.main_engine)
            req=converter.convert_order_request(req,lock=False,net=False)[0]
        except:
            pass

        vt_orderid: str = self.main_engine.send_order(req, contract.gateway_name)
        return vt_orderid

这样改之后从我目前的使用情况来看可以稳定解决平今转换的问题,如果有用脚本策略的大神有更好的修改意见欢迎讨论!

Member
avatar
加入于:
帖子: 1448
声望: 102

感谢分享!

© 2015-2022 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】