用的时候会发现,持仓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['PositionCost'] / data['Position'] / contract.size

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

            if data['OptionValue'] < 0:
                pnl = data['PositionCost'] - abs(data['OptionValue'])
            elif data['OptionValue'] > 0:
                pnl = data['OptionValue'] - avg_price
            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