VeighNa量化社区
你的开源社区量化交易平台
miro's Avatar
Member
离线
42 帖子
声望: 2

xiaohe wrote:

veighna_studio版本是?最近新增了广期所的合约,vnpy_ctp需要升级

目前用的是2.8,是融航的接口会出现这个问题,ctp至今没遇到过。跟广期所应该没关系,没有交易过工业硅,其他几个交易所的似乎都遇到过

最近偶尔会碰到收不到订单回报的情况,我用的是脚本策略,下单之后用get_order(vt_orderid)查询订单,发现status一直是SUBMITTING,但实际已经成交了;如果重新登陆账号,又可以接收到这笔订单的回报,也可以查到ALLTRADED的状态。
我看了一下源码,发现orderdata对象在生成的时候默认的status就是SUBMITTING,后续可能没有收到回报,所以没有触发on_order()来更新内存里的数据,不知道是不是因为网络原因有丢包之类的情况?

miro wrote:

看了一下源码,这里取tradedata的时候是检查相同的orderid,但是在自成交的时候一个tradeid会对应两个orderid,这里处理成交推送的时候是用tradeid保存信息的,所以会导致其中一个orderid查到的trade少这一条。这么看的话如果底层保存的时候用tradeid和orderid做二元键应该可以避免这个问题?就是不知道自成交是否会推送两次数据?

def get_trades(self, vt_orderid: str, use_df: bool = False) -> Sequence[TradeData]:
        """"""
        trades: list = []
        all_trades: List[TradeData] = self.main_engine.get_all_trades()

        for trade in all_trades:
            if trade.vt_orderid == vt_orderid:
                trades.append(trade)

        if not use_df:
            return trades
        else:
            return to_df(trades)
def get_all_trades(self) -> List[TradeData]:
        """
        Get all trade data.
        """
        return list(self.trades.values())
def process_trade_event(self, event: Event) -> None:
        """"""
        trade: TradeData = event.data
        self.trades[trade.vt_tradeid] = trade

好了,尝试了一下,把process_trade_event()修改了一下,改成 self.trades[(trade.vt_orderid,trade.vt_tradeid)]=trade 之后,重连之后查询成交就得到两笔成交了。
description

看了一下源码,这里取tradedata的时候是检查相同的orderid,但是在自成交的时候一个tradeid会对应两个orderid,这里处理成交推送的时候是用tradeid保存信息的,所以会导致其中一个orderid查到的trade少这一条。这么看的话如果底层保存的时候用tradeid和orderid做二元键应该可以避免这个问题?就是不知道自成交是否会推送两次数据?

def get_trades(self, vt_orderid: str, use_df: bool = False) -> Sequence[TradeData]:
        """"""
        trades: list = []
        all_trades: List[TradeData] = self.main_engine.get_all_trades()

        for trade in all_trades:
            if trade.vt_orderid == vt_orderid:
                trades.append(trade)

        if not use_df:
            return trades
        else:
            return to_df(trades)
def get_all_trades(self) -> List[TradeData]:
        """
        Get all trade data.
        """
        return list(self.trades.values())
def process_trade_event(self, event: Event) -> None:
        """"""
        trade: TradeData = event.data
        self.trades[trade.vt_tradeid] = trade

xiaohe wrote:

脚本策略模板的get_position函数是获取的底层持仓,不会出错的
这个我知道,不过是多个策略在跑,所以还是得在本地做一下记录,区分清楚。主要是想问下这个问题怎么处理?是因为每一个成交订单都只会推送一次,所以对应的时候缺了一边吗?

目前用的是2.9版本的脚本策略,我想要在集合竞价的时候平掉股指期货的对锁单,但是出现了成交编号相同的情况,也就是成交编号为277的那一笔是自成交。用get_order可以看到两笔订单都是成交了4手的,但是用get_trade会发现自成交的那一手只记录在了其中一边的信息之中,这会导致我更新本地仓位时出现问题,应该怎么解决呢?
description

description

这个我没用过,不知道有没有计算的方法。simnow就是仿真交易,你这里登录的账号应该也是simnow的,可以直接交易试试,启动的时候不需要勾选本地仿真模块

https://www.vnpy.com/docs/cn/paper_account.html#id8
看起来是本地仿真环境吧,这个文档应该可以解决你的问题

我也遇到了,但似乎不影响实际使用

业务原因,需要同时操作融航和CTP的账户,自己搞了个多账户监控的程序,但是想要在一个程序里同时登录融航和CTP没法实现。从二者的gateway代码中没有看出来哪里会出现冲突,是因为二者的tdapi底层有不兼容的问题?

如题,目前有多个账户在交易,开多个快期客户端看起来很麻烦,目前是想找个办法对这些账户的资金、报单、成交等信息在一个面板上进行监控,不知道有没有比较方便的办法?目前知道的是快期专业版可以,但免费账户只能绑定两个实盘账号。

之前在用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

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

xiaohe wrote:

miro wrote:

最近出现了这样一个问题:集合竞价的订单没有成交,开盘后我想撤掉这个单子重新下,发出撤单指令后等待了1秒用get_order()查询订单状态得到的是Cancelled,但是在快期上看到的实际是已成交。这个就很迷惑,想知道这个撤单流程和查订单状态哪里出了问题。
可以打印一下onRtnOrder函数看看收到的数据

好的谢谢,我用的是脚本策略,把onRtnOrder的数据写到日志里了,看看下次出现这个问题能不能查清楚

当然这个出错的频率比较低,一般是集合竞价挂的价格和开盘价差的不大,开盘后没来得及撤掉就被hit到了,但是遇到之后就会导致我的程序以为没成交又追了一个单,实际的仓位和我的目标差了很多

最近出现了这样一个问题:集合竞价的订单没有成交,开盘后我想撤掉这个单子重新下,发出撤单指令后等待了1秒用get_order()查询订单状态得到的是Cancelled,但是在快期上看到的实际是已成交。这个就很迷惑,想知道这个撤单流程和查订单状态哪里出了问题。

simnow7.9就开始维护了,据说要停一个月

郭易燔 wrote:

Status是指委托的状态,一般有提交中、未成交、部分成交、全部成交、拒单、全部撤单这些状态。这些状态的更新是由交易所确定并推送的。
委托时间不同是因为这是两个委托状态不同。
你可以在get_order时查看一下订单状态,只有在全撤或者全部成交时才算这个委托结束了,不然之后还会有委托状态更新的。
好的谢谢,看来更新本地信息的时候得把没完成的订单再返回回来继续循环。只是比较纠结这个submitting的时长是受什么影响的,按理说2秒都够报单加成交了,延迟也应该没这么大吧...

最近我的实盘策略在开盘发FAK单进行仓位调整时,出现了好几次重复下单的情况,排查之后发现是get_order()查询订单信息显示Status.SUBMITTING且get_trades()没有成交回报,所以没有能更新存储在本地文件中的仓位信息,之后导致重复下单。
我执行的整体逻辑是这样的:

while trading:
    if need_order:
        orders_list.append(order(...))
    sleep(1.5)
    get_orders(order_list)
    update_positions(...)
    orders_list=[]

是否是因为中间延迟比较大,我等待的时间不够长?但模拟盘相同的程序且等待时间更短没有出现这种情况,实盘上交所的品种也是正常的,唯独最近大商所品种频繁出现。然后我又检查了一下订单时间等信息,发现用get_order()获取到的time与快期显示的有很大差异,比如下图中本地记录下来的是21:00:04,快期中则是21:00:10,所以想请问一下这种情况怎么处理呢?
description

description

如题,我用的是这个帖子里的脚本https://www.vnpy.com/forum/topic/3046-quan-shi-chang-lu-zhi-xing-qing-shu-ju
然后运行之后只有如图的结果,数据库里并没有更新数据。是因为更新版本之后有一些代码失效了吗?
description

xiaohe wrote:

self.pos就是记录的策略持仓

我用的是jupyter,我看scriptengine的源码里好像没有这个?只有get_all_positions()获取账户的底层持仓

© 2015-2022 微信 18391752892
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

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