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

最后看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出来的就是下面的这个

以下是我修改的
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()
之后再打印出来就有了

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

