vn.py官网
开源量化社区
Member
avatar
加入于:
帖子: 236
声望: 56

1. 一个叫“人海孤鸿”的vnpy会员,他要下单1.1手!

今天一个叫“人海孤鸿”的vnpy会员找到我,他在模拟交易比特币相关的合约BTC-USD。
微信中他反应在用CTA策略不能下单小数手的BTC-USD合约。
小数手数,真的吗?

2. 他真的可以手工下单1.1手,但是他的CTA策略却不可以!

如下图所所示,他一共操作了两次,一次是手工买开0.1手,成功;
一次是用CTA策略卖开1.1手,可是只能卖开1手。
一次是用CTA策略卖开2.7手,可是却能卖开3手。(貌似还四舍五入!)
description

3. 为什么,问题在PaperAccount ?

后经过交流才知道,原来他是使用PaperAccount 做模拟交易。

3.1 PaperAccount把main_engine的send_order给接管了

当使用PaperAccount时,cta_template中的send_order()并没有调用具体接口的send_order(),而是调用了PaperAccount的send_order()。
因为PaperAccount应用(app)的engine在

class PaperEngine(BaseEngine):
    """"""
    setting_filename = "paper_account_setting.json"
    data_filename = "paper_account_data.json"

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super().__init__(main_engine, event_engine, APP_NAME)

        self.trade_slippage: int = 0
        self.timer_interval: int = 3
        self.instant_trade: bool = False

        self.order_count: int = 100000
        self.timer_count: int = 0

        self.active_orders: Dict[str, Dict[str, OrderData]] = {}
        self.gateway_map: Dict[str, str] = {}
        self.ticks: Dict[str, TickData] = {}
        self.positions: Dict[Tuple[str, Direction], PositionData] = {}

        # Patch main engine functions
        self._subscribe = main_engine.subscribe
        self._query_history = main_engine.query_history

        main_engine.subscribe = self.subscribe
        main_engine.query_history = self.query_history
        main_engine.send_order = self.send_order       # 在这里把main_engine的send_order给接管了!
        main_engine.cancel_order = self.cancel_order  # 在这里把main_engine的cancel_order 给接管了!

        self.load_setting()
        self.load_data()
        self.register_event()

3.2 PaperAccount的send_order()

PaperAccount的send_order()没有对委托手数进行检查,代码如下:

    def send_order(self, req: OrderRequest, gateway_name: str) -> str:
        """"""
        contract: ContractData = self.main_engine.get_contract(req.vt_symbol)
        if not contract:
            self.write_log(f"委托失败,找不到该合约{req.vt_symbol}")
            return ""

        self.order_count += 1
        now = datetime.now().strftime("%y%m%d%H%M%S")
        orderid = now + str(self.order_count)
        vt_orderid = f"{GATEWAY_NAME}.{orderid}"

        # Put simulated order update event from gateway
        order = req.create_order_data(orderid, GATEWAY_NAME)
        self.put_event(EVENT_ORDER, copy(order))

        # Check if order is valid
        updated_position = self.check_order_valid(order, contract)

        # Put simulated order update event from exchange
        if order.status != Status.REJECTED:
            order.datetime = datetime.now(LOCAL_TZ)
            order.status = Status.NOTTRADED
            active_orders = self.active_orders.setdefault(order.vt_symbol, {})
            active_orders[orderid] = order

        self.put_event(EVENT_ORDER, copy(order))

        # Update position frozen for close order
        if updated_position:
            self.put_event(EVENT_POSITION, copy(updated_position))

        # Cross order immediately with last tick data
        if self.instant_trade:
            tick = self.ticks.get(order.vt_symbol, None)
            if tick:
                self.cross_order(order, tick)

                if not order.is_active():
                    active_orders = self.active_orders[order.vt_symbol]
                    active_orders.pop(orderid)

        return vt_orderid

PaperAccount可以让你的策略开仓小数手,这是错误的 !

4. 实际接口中的send_order()都不可以

4.1 CTP的网关的send_order()不可以是小数手数

见文件vnpy_ctp\gateway\gateway.py

    def send_order(self, req: OrderRequest) -> str:
        """委托下单"""
        if req.offset not in OFFSET_VT2CTP:
            self.gateway.write_log("请选择开平方向")
            return ""

        if req.type not in ORDERTYPE_VT2CTP:
            self.gateway.write_log(f"当前接口不支持该类型的委托{req.type.value}")
            return ""

        self.order_ref += 1

        ctp_req: dict = {
            "InstrumentID": req.symbol,
            "ExchangeID": req.exchange.value,
            "LimitPrice": req.price,
            "VolumeTotalOriginal": int(req.volume),            # ---- 注意:这里做了取整
            "OrderPriceType": ORDERTYPE_VT2CTP.get(req.type, ""),
            "Direction": DIRECTION_VT2CTP.get(req.direction, ""),
            "CombOffsetFlag": OFFSET_VT2CTP.get(req.offset, ""),
            "OrderRef": str(self.order_ref),
            "InvestorID": self.userid,
            "UserID": self.userid,
            "BrokerID": self.brokerid,
            "CombHedgeFlag": THOST_FTDC_HF_Speculation,
            "ContingentCondition": THOST_FTDC_CC_Immediately,
            "ForceCloseReason": THOST_FTDC_FCC_NotForceClose,
            "IsAutoSuspend": 0,
            "TimeCondition": THOST_FTDC_TC_GFD,
            "VolumeCondition": THOST_FTDC_VC_$,
            "MinVolume": 1
        }

        if req.type == OrderType.FAK:
            ctp_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice
            ctp_req["TimeCondition"] = THOST_FTDC_TC_IOC
            ctp_req["VolumeCondition"] = THOST_FTDC_VC_$
        elif req.type == OrderType.FOK:
            ctp_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice
            ctp_req["TimeCondition"] = THOST_FTDC_TC_IOC
            ctp_req["VolumeCondition"] = THOST_FTDC_VC_CV

        self.reqid += 1
        self.reqOrderInsert(ctp_req, self.reqid)

        orderid: str = f"{self.frontid}_{self.sessionid}_{self.order_ref}"
        order: OrderData = req.create_order_data(orderid, self.gateway_name)
        self.gateway.on_order(order)

        return order.vt_orderid

4.2 火币永续网关的send_order()不可以是小数手数

见文件vnpy\gateway\huobis\huobis_gateway.py

    def send_order(self, req: OrderRequest) -> str:
        """"""
        if self.usdt_base:
            path = "/linear-swap-api/v1/swap_cross_order"
        else:
            path = "/swap-api/v1/swap_order"

        local_orderid = self.new_local_orderid()
        order = req.create_order_data(
            local_orderid,
            self.gateway_name
        )
        order.datetime = datetime.now(CHINA_TZ)

        data = {
            "contract_code": req.symbol,
            "client_order_id": int(local_orderid),
            "price": req.price,
            "volume": int(req.volume),    # ---- 注意:这里做了取整
            "direction": DIRECTION_VT2HUOBIS.get(req.direction, ""),
            "offset": OFFSET_VT2HUOBIS.get(req.offset, ""),
            "order_price_type": ORDERTYPE_VT2HUOBIS.get(req.type, ""),
            "lever_rate": 20
        }

        self.add_request(
            method="POST",
            path=path,
            callback=self.on_send_order,
            data=data,
            extra=order,
            on_error=self.on_send_order_error,
            on_failed=self.on_send_order_failed
        )

        self.gateway.on_order(order)
        return order.vt_orderid
Administrator
avatar
加入于:
帖子: 4749
声望: 278

确实对新手来说这里会引起混淆,我们后面来改下

Member
avatar
加入于:
帖子: 60
声望: 0

必须给个赞!

Member
avatar
加入于:
帖子: 16
声望: 0

请问在币安也有同样的问题吗?是不是可以把 Int去掉就解决了这个问题。不过去掉Int之后,那么其他的策略,例如股指期货策略是不是会受影响?对于新手来讲怎么解决最好?

谢谢

Member
avatar
加入于:
帖子: 236
声望: 56

linhertz wrote:

请问在币安也有同样的问题吗?是不是可以把 Int去掉就解决了这个问题。不过去掉Int之后,那么其他的策略,例如股指期货策略是不是会受影响?对于新手来讲怎么解决最好?

谢谢

不可以。因为去掉int虽然可以在PaperAccount模拟时能够下单,但是一旦实盘交易是无法开仓比合约资料中规定的最小手数的。

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

沪公网安备 31011502017034号