VeighNa量化社区
你的开源社区量化交易平台 | vn.py | vnpy
Member
avatar
加入于:
帖子: 32
声望: 1

用的时候会发现,持仓Event的PositionData始终显示盈亏为0,如下图
description

最后看soptgateway底层代码的时候发现,原来用来计算盈亏的price始终为0,所以盈亏始终为0,gateway的计算逻辑如下图

def onRspQryInvestorPosition(self, data: dict, error: dict, reqid: int, last: bool) -> None:
        """持仓查询回报"""
        if not data:
            return

        # 必须已经收到了合约信息后才能处理
        symbol: str = data["InstrumentID"]
        contract: ContractData = symbol_contract_map.get(symbol, None)

        if contract:
            # 获取之前缓存的持仓数据缓存
            key: str = f"{data['InstrumentID'], data['PosiDirection']}"
            position: PositionData = self.positions.get(key, None)

            if "&" in symbol:
                exchange: Exchange = Exchange.SSE
            else:
                exchange = contract.exchange

            if not position:
                position = PositionData(
                    symbol=symbol,
                    exchange=exchange,
                    direction=DIRECTION_SOPT2VT[data["PosiDirection"]],
                    gateway_name=self.gateway_name
                )
                self.positions[key] = position

            # 计算昨仓
            position.yd_volume = data["Position"] - data["TodayPosition"]

            # 获取合约的乘数信息
            size: int = contract.size

            # 计算之前已有仓位的持仓总成本
            cost: float = position.price * position.volume * size

            # 累加更新持仓数量和盈亏
            position.volume += data["Position"]
            position.pnl += data["PositionProfit"]

            # 计算更新后的持仓总成本和均价
            if position.volume and size:
                cost += data["PositionCost"]
                position.price = cost / (position.volume * size)

            # 更新仓位冻结数量
            if position.direction == Direction.LONG:
                position.frozen += data["ShortFrozen"]
            else:
                position.frozen += data["LongFrozen"]

        if last:
            for position in self.positions.values():
                self.gateway.on_position(position)

            self.positions.clear()

这个函数会返回某个合约的持仓情况,并统一转换成PositionData输出出来。可以看到在创建PositionData之前,没有定义持仓的这个合约的成本价price,所以创建PositionData的时候price的默认值是0,故之后cost始终为0。而代表持仓盈亏的pnl参数是由data这个字典的PositionProfit决定的。通过打印收到的data观察,这个字段gateway是不会计算的,始终是0,所以pnl要自己算了。而且昨仓的计算也很奇怪,data的Position字段代表现有持仓手数,TodayPosition也代表现有持仓手数,两者相减·····,所以我还是用data本身提供的YdPosition字段来替换。上面的函数打印data和contract出来的就是下面的这个

description
以下是我修改的

    def onRspQryInvestorPosition(self, data: dict, error: dict, reqid: int, last: bool) -> None:
        """持仓查询回报"""
        if not data:
            return

        # 必须已经收到了合约信息后才能处理
        symbol: str = data["InstrumentID"]
        contract: ContractData = symbol_contract_map.get(symbol, None)

        if contract:
            # print(f'收到的持仓数据data: {data}')
            # print(f'对应的合约contract: {contract.__dict__}')
            # 获取之前缓存的持仓数据缓存
            key: str = f"{data['InstrumentID'], data['PosiDirection']}"
            position: PositionData = self.positions.get(key, None)

            if "&" in symbol:
                exchange: Exchange = Exchange.SSE
            else:
                exchange = contract.exchange

            if data['Position'] == 0:
                avg_price = 0.0
            else:
                avg_price = data['OpenCost'] / data['Position'] / contract.size

            # 计算昨仓
            yd_volume = data['YdPosition']

            if data['OptionValue'] < 0:
                pnl = data['OptionValue'] + data['OpenCost']
            elif data['OptionValue'] > 0:
                pnl = data['OptionValue'] - data['OpenCost']
            else:
                pnl = 0

            if not position:
                position = PositionData(
                    symbol=symbol,
                    exchange=exchange,
                    direction=DIRECTION_SOPT2VT[data["PosiDirection"]],
                    price=avg_price,
                    yd_volume=yd_volume,
                    volume=data["Position"],
                    pnl=pnl,
                    gateway_name=self.gateway_name
                )
                self.positions[key] = position

            # 更新仓位冻结数量
            if position.direction == Direction.LONG:
                position.frozen += data["ShortFrozen"]
            else:
                position.frozen += data["LongFrozen"]

        if last:
            for position in self.positions.values():
                self.gateway.on_position(position)

            self.positions.clear()

之后再打印出来就有了

description

跟客户端上显示一致,如下

description

Member
avatar
加入于:
帖子: 1983
声望: 154

感谢反馈,这块应该是SOPT柜台的特殊情况,期货确认是有PNL的,需要的同学可以参考后自己改下

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

沪公网安备 31011502017034号

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