VeighNa量化社区
你的开源社区量化交易平台
Member
avatar
加入于:
帖子: 420
声望: 181

1. 不要迷恋停止单,它缺点多的很

当你想以比市场价更高的价格买,或者以比市场价更低的价格卖时,使用send_order()是会立即执行的,但是用停止单却可以做到这一点,这是停止单的优点。
但是实际使用中停止单也是有缺点的:

  • 当以比市场价更低的价格买,它会立即被执行
  • 当以比市场价更高的价格卖,它会立即被执行
  • 实际运行中有多个停止单通知满足条件,接口在极短时间内接受多个停止单发出的委托,发生委托覆盖。表现为明明策略发出过多个停止单,但是只有最后一个停止单有结果,其他的委托莫名其妙的人间蒸发了,不见了,接口不回应、不通知,策略也不知道,用户无法查。
  • 停止单一经发出,触发价也是执行价,无法根据当时的市场价格做出价格调整
  • 只有CTA策略才可以使用停止单,其他类型的策略无法使用,因为它的执行逻辑和具体合约耦合度太高

2. 什么是条件单?

这是本人给它取的名字,它其实是本人以前提到的交易线(TradeLine)的改进和增强。
它主要就是为解决停止单上述缺点而设计的,当然应该具备上述优点。

  • 可以设定触发价格和触发条件
  • 触发条件包括四种:>=、<=、>、<,与执行的委托方向无关
  • 当市场价格满足触发条件时,条件单立即执行,执行收最小流控限制
  • 执行价格可以是触发价、市场价或极限价(买时为涨停价,卖时为跌停价)
  • 条件单管理器还可以提供手工取消条件单的功能,双击就可以取消

先看一眼条件单的效果图

description

2.1 它数据结构包含如下:

在vnpy_ctastrategy\base.py中增加如下代码:

class Condition(Enum):  # hxxjava add
    """ 条件单的条件 """
    BT = ">"
    LT = "<"
    BE = ">="
    LE = "<="

class ExecutePrice(Enum):  # hxxjava add
    """ 执行价格 """
    SETPRICE = "设定价"
    MARKET = "市场价"
    EXTREME = "极限价"

class CondOrderStatus(Enum):    # hxxjava add
    """ 条件单状态 """
    WAITING = "等待中"
    CANCELLED = "已撤销"
    TRIGGERED = "已触发"

@dataclass
class ConditionOrder:   # hxxjava add
    """ 条件单 """
    strategy_name: str
    vt_symbol: str
    direction: Direction
    offset: Offset
    price: float
    volume: float
    condition:Condition 
    execute_price:ExecutePrice = ExecutePrice.SETPRICE
    create_time: datetime = datetime.now()
    trigger_time: datetime = None
    cond_orderid: str = ""  # 条件单编号
    status: CondOrderStatus = CondOrderStatus.WAITING

    def __post_init__(self):
        """  """
        if not self.cond_orderid:
            self.cond_orderid = datetime.now().strftime("%m%d%H%M%S%f")[:13]

EVENT_CONDITION_ORDER = "eConditionOrder"       # hxxjava add

2.2 条件单管理器

2.2.1 修改CTA策略管理器

修改vnpy_ctastrategy\ui\widget.py中的class CtaManager,代码如下:

class CtaManager(QtWidgets.QWidget):
    """"""

    signal_log: QtCore.Signal = QtCore.Signal(Event)
    signal_strategy: QtCore.Signal = QtCore.Signal(Event)

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
        """"""
        super().__init__()

        self.main_engine: MainEngine = main_engine
        self.event_engine: EventEngine = event_engine
        self.cta_engine: CtaEngine = main_engine.get_engine(APP_NAME)

        self.managers: Dict[str, StrategyManager] = {}

        self.init_ui()
        self.register_event()
        self.cta_engine.init_engine()
        self.update_class_combo()

    def init_ui(self) -> None:
        """"""
        self.setWindowTitle("CTA策略")

        # Create widgets
        self.class_combo: QtWidgets.QComboBox = QtWidgets.QComboBox()

        add_button: QtWidgets.QPushButton = QtWidgets.QPushButton("添加策略")
        add_button.clicked.connect(self.add_strategy)

        init_button: QtWidgets.QPushButton = QtWidgets.QPushButton("全部初始化")
        init_button.clicked.connect(self.cta_engine.init_all_strategies)

        start_button: QtWidgets.QPushButton = QtWidgets.QPushButton("全部启动")
        start_button.clicked.connect(self.cta_engine.start_all_strategies)

        stop_button: QtWidgets.QPushButton = QtWidgets.QPushButton("全部停止")
        stop_button.clicked.connect(self.cta_engine.stop_all_strategies)

        clear_button: QtWidgets.QPushButton = QtWidgets.QPushButton("清空日志")
        clear_button.clicked.connect(self.clear_log)

        roll_button: QtWidgets.QPushButton = QtWidgets.QPushButton("移仓助手")
        roll_button.clicked.connect(self.roll)

        self.scroll_layout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
        self.scroll_layout.addStretch()

        scroll_widget: QtWidgets.QWidget = QtWidgets.QWidget()
        scroll_widget.setLayout(self.scroll_layout)

        self.scroll_area: QtWidgets.QScrollArea = QtWidgets.QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setWidget(scroll_widget)

        self.log_monitor: LogMonitor = LogMonitor(self.main_engine, self.event_engine)

        self.stop_order_monitor: StopOrderMonitor = StopOrderMonitor(
            self.main_engine, self.event_engine
        )

        self.strategy_combo = QtWidgets.QComboBox()
        self.strategy_combo.setMinimumWidth(200)
        find_button = QtWidgets.QPushButton("查找")
        find_button.clicked.connect(self.find_strategy)

        # hxxjava add
        self.condition_order_monitor = ConditionOrderMonitor(self.cta_engine)

        # Set layout
        hbox1: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
        hbox1.addWidget(self.class_combo)
        hbox1.addWidget(add_button)
        hbox1.addStretch()
        hbox1.addWidget(self.strategy_combo)
        hbox1.addWidget(find_button)
        hbox1.addStretch()
        hbox1.addWidget(init_button)
        hbox1.addWidget(start_button)
        hbox1.addWidget(stop_button)
        hbox1.addWidget(clear_button)
        hbox1.addWidget(roll_button)

        grid = QtWidgets.QGridLayout()
        # grid.addWidget(self.scroll_area, 0, 0, 2, 1)
        grid.addWidget(self.scroll_area, 0, 0, 3, 1) # hxxjava change 3 rows , 1 column 
        grid.addWidget(self.stop_order_monitor, 0, 1)
        grid.addWidget(self.condition_order_monitor, 1, 1)  # hxxjava add
        # grid.addWidget(self.log_monitor, 1, 1)
        grid.addWidget(self.log_monitor, 2, 1)  # hxxjava change
        vbox: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox1)
        vbox.addLayout(grid)

        self.setLayout(vbox)

    def update_class_combo(self) -> None:
        """"""
        names = self.cta_engine.get_all_strategy_class_names()
        names.sort()
        self.class_combo.addItems(names)

    def update_strategy_combo(self) -> None:
        """"""
        names = list(self.managers.keys())
        names.sort()

        self.strategy_combo.clear()
        self.strategy_combo.addItems(names)

    def register_event(self) -> None:
        """"""
        self.signal_strategy.connect(self.process_strategy_event)

        self.event_engine.register(
            EVENT_CTA_STRATEGY, self.signal_strategy.emit
        )

    def process_strategy_event(self, event) -> None:
        """
        Update strategy status onto its monitor.
        """
        data = event.data
        strategy_name: str = data["strategy_name"]

        if strategy_name in self.managers:
            manager: StrategyManager = self.managers[strategy_name]
            manager.update_data(data)
        else:
            manager: StrategyManager = StrategyManager(self, self.cta_engine, data)
            self.scroll_layout.insertWidget(0, manager)
            self.managers[strategy_name] = manager

            self.update_strategy_combo()

    def remove_strategy(self, strategy_name) -> None:
        """"""
        manager: StrategyManager = self.managers.pop(strategy_name)
        manager.deleteLater()

        self.update_strategy_combo()

    def add_strategy(self) -> None:
        """"""
        class_name: str = str(self.class_combo.currentText())
        if not class_name:
            return

        parameters: dict = self.cta_engine.get_strategy_class_parameters(class_name)
        editor: SettingEditor = SettingEditor(parameters, class_name=class_name)
        n: int = editor.exec_()

        if n == editor.Accepted:
            setting: dict = editor.get_setting()
            vt_symbol: str = setting.pop("vt_symbol")
            strategy_name: str = setting.pop("strategy_name")

            self.cta_engine.add_strategy(
                class_name, strategy_name, vt_symbol, setting
            )

    def find_strategy(self) -> None:
        """"""
        strategy_name = self.strategy_combo.currentText()
        manager = self.managers[strategy_name]
        self.scroll_area.ensureWidgetVisible(manager)

    def clear_log(self) -> None:
        """"""
        self.log_monitor.setRowCount(0)

    def show(self) -> None:
        """"""
        self.showMaximized()

    def roll(self) -> None:
        """"""
        dialog: RolloverTool = RolloverTool(self)
        dialog.exec_()

2.2.2 条件单管理器代码

在vnpy_ctastrategy\ui\widget.py中增加如下代码:

class ConditionOrderMonitor(BaseMonitor):   # hxxjava add
    """
    Monitor for condition order.
    """

    event_type = EVENT_CONDITION_ORDER
    data_key = "cond_orderid"
    sorting = True

    headers = {
        "cond_orderid": {
            "display": "条件单号",
            "cell": BaseCell,
            "update": False,
        },
        "vt_symbol": {"display": "本地代码", "cell": BaseCell, "update": False},
        "direction": {"display": "方向", "cell": EnumCell, "update": False},
        "offset": {"display": "开平", "cell": EnumCell, "update": False},
        "price": {"display": "触发价", "cell": BaseCell, "update": False},
        "volume": {"display": "数量", "cell": BaseCell, "update": False},
        "condition": {"display": "触发条件", "cell": EnumCell, "update": False},
        "execute_price": {"display": "执行价", "cell": EnumCell, "update": False},
        "create_time": {"display": "生成时间", "cell": TimeCell, "update": False},
        "trigger_time": {"display": "触发时间", "cell": TimeCell, "update": False},
        "status": {"display": "状态", "cell": EnumCell, "update": True},
        "strategy_name": {"display": "策略名称", "cell": BaseCell, "update": False},
    }

    def __init__(self,cta_engine : MyCtaEngine):
        """"""
        super().__init__(cta_engine.main_engine, cta_engine.event_engine)

        self.cta_engine = cta_engine

    def init_ui(self):
        """
        Connect signal.
        """
        super().init_ui()

        self.setToolTip("双击单元格可停止条件单")
        self.itemDoubleClicked.connect(self.stop_condition_order)

    def stop_condition_order(self, cell):
        """
        Stop algo if cell double clicked.
        """
        order = cell.get_data()
        if order:
            self.cta_engine.cancel_condition_order(order.cond_orderid)

2.2.3 加载条件单管理器

修改策略管理器StrategyManager的代码如下:

class StrategyManager(QtWidgets.QFrame):
    """
    Manager for a strategy
    """

    def __init__(
        self, cta_manager: CtaManager, cta_engine: CtaEngine, data: dict
    ):
        """"""
        super(StrategyManager, self).__init__()

        self.cta_manager = cta_manager
        self.cta_engine = cta_engine

        self.strategy_name = data["strategy_name"]
        self._data = data

        self.tradetool : TradingWidget = None                    # hxxjava add

        self.init_ui()

    def init_ui(self):
        """"""
        self.setFixedHeight(300)
        self.setFrameShape(self.Box)
        self.setLineWidth(1)

        self.init_button = QtWidgets.QPushButton("初始化")
        self.init_button.clicked.connect(self.init_strategy)

        self.start_button = QtWidgets.QPushButton("启动")
        self.start_button.clicked.connect(self.start_strategy)
        self.start_button.setEnabled(False)

        self.stop_button = QtWidgets.QPushButton("停止")
        self.stop_button.clicked.connect(self.stop_strategy)
        self.stop_button.setEnabled(False)

        self.trade_button = QtWidgets.QPushButton("交易")       # hxxjava add
        self.trade_button.clicked.connect(self.show_tradetool)  # hxxjava add
        self.trade_button.setEnabled(False)                      # hxxjava add

        self.edit_button = QtWidgets.QPushButton("编辑")
        self.edit_button.clicked.connect(self.edit_strategy)

        self.remove_button = QtWidgets.QPushButton("移除")
        self.remove_button.clicked.connect(self.remove_strategy)

        strategy_name = self._data["strategy_name"]
        vt_symbol = self._data["vt_symbol"]
        class_name = self._data["class_name"]
        author = self._data["author"]

        label_text = (
            f"{strategy_name}  -  {vt_symbol}  ({class_name} by {author})"
        )
        label = QtWidgets.QLabel(label_text)
        label.setAlignment(QtCore.Qt.AlignCenter)

        self.parameters_monitor = DataMonitor(self._data["parameters"])
        self.variables_monitor = DataMonitor(self._data["variables"])

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(self.init_button)
        hbox.addWidget(self.start_button)
        hbox.addWidget(self.trade_button)      # hxxjava add
        hbox.addWidget(self.stop_button)
        hbox.addWidget(self.edit_button)
        hbox.addWidget(self.remove_button)

        # hxxjava change to self.vbox,old is vbox
        self.vbox = QtWidgets.QVBoxLayout()     
        self.vbox.addWidget(label)
        self.vbox.addLayout(hbox)
        self.vbox.addWidget(self.parameters_monitor)
        self.vbox.addWidget(self.variables_monitor)
        self.setLayout(self.vbox)

    def update_data(self, data: dict):
        """"""
        self._data = data

        self.parameters_monitor.update_data(data["parameters"])
        self.variables_monitor.update_data(data["variables"])

        # Update button status
        variables = data["variables"]
        inited = variables["inited"]
        trading = variables["trading"]

        if not inited:
            return
        self.init_button.setEnabled(False)

        if trading:
            self.start_button.setEnabled(False)
            self.trade_button.setEnabled(True)  # hxxjava
            self.stop_button.setEnabled(True)
            self.edit_button.setEnabled(False)
            self.remove_button.setEnabled(False)
        else:
            self.start_button.setEnabled(True)
            self.trade_button.setEnabled(False)  # hxxjava
            self.stop_button.setEnabled(False)
            self.edit_button.setEnabled(True)
            self.remove_button.setEnabled(True)

    def init_strategy(self):
        """"""
        self.cta_engine.init_strategy(self.strategy_name)

    def start_strategy(self):
        """"""
        self.cta_engine.start_strategy(self.strategy_name)

    def show_tradetool(self):   # hxxjava add
        """ 为策略显示交易工具 """
        if not self.tradetool:
            strategy = self.cta_engine.strategies.get(self.strategy_name,None)
            if strategy and strategy.trading:
                self.tradetool = TradingWidget(strategy,self.cta_engine.event_engine)
                self.vbox.addWidget(self.tradetool)

        else:
            is_visible = self.tradetool.isVisible()
            self.tradetool.setVisible(not is_visible)

    def stop_strategy(self):
        """"""
        self.cta_engine.stop_strategy(self.strategy_name)

    def edit_strategy(self):
        """"""
        strategy_name = self._data["strategy_name"]

        parameters = self.cta_engine.get_strategy_parameters(strategy_name)
        editor = SettingEditor(parameters, strategy_name=strategy_name)
        n = editor.exec_()

        if n == editor.Accepted:
            setting = editor.get_setting()
            self.cta_engine.edit_strategy(strategy_name, setting)

    def remove_strategy(self):
        """"""
        result = self.cta_engine.remove_strategy(self.strategy_name)

        # Only remove strategy gui manager if it has been removed from engine
        if result:
            self.cta_manager.remove_strategy(self.strategy_name)

2.2.4 交易组件

创建vnpy\usertools\trading_widget.py文件,其中内容:

""" 
条件单交易组件 
作者:hxxjava
日线:2022-5-10
"""
from vnpy.trader.ui import QtCore, QtWidgets, QtGui

from vnpy.trader.constant import Direction,Offset
from vnpy.trader.event import EVENT_TICK
from vnpy.event.engine import Event,EventEngine 
from vnpy_ctastrategy.base import Condition,CondOrderStatus,ExecutePrice,ConditionOrder
from vnpy_ctastrategy.template import CtaTemplate


class TradingWidget(QtWidgets.QWidget):
    """
    CTA strategy manual trading widget.
    """

    signal_tick = QtCore.pyqtSignal(Event)

    def __init__(self, strategy: CtaTemplate, event_engine: EventEngine):
        """"""
        super().__init__()

        self.strategy: CtaTemplate = strategy
        self.event_engine: EventEngine = event_engine

        self.vt_symbol: str = strategy.vt_symbol
        self.price_digits: int = 0

        self.init_ui()
        self.register_event()

    def init_ui(self) -> None:
        """"""
        # 交易方向:多/空
        self.direction_combo = QtWidgets.QComboBox()
        self.direction_combo.addItems(
            [Direction.LONG.value, Direction.SHORT.value])

        # 开平选择:开/平
        self.offset_combo = QtWidgets.QComboBox()
        self.offset_combo.addItems([offset.value for offset in Offset])

        # 条件类型
        conditions = [Condition.BE,Condition.LE,Condition.BT,Condition.LT]
        self.condition_combo = QtWidgets.QComboBox()
        self.condition_combo.addItems(
            [condition.value for condition in conditions])

        double_validator = QtGui.QDoubleValidator()
        double_validator.setBottom(0)

        self.price_line = QtWidgets.QLineEdit()
        self.price_line.setValidator(double_validator)

        self.exit_line = QtWidgets.QLineEdit()
        self.exit_line.setValidator(double_validator)

        self.volume_line = QtWidgets.QLineEdit()
        self.volume_line.setValidator(double_validator)

        self.price_check = QtWidgets.QCheckBox()
        self.price_check.setToolTip("设置价格随行情更新")

        execute_prices = [ExecutePrice.SETPRICE,ExecutePrice.MARKET,ExecutePrice.EXTREME]
        self.execute_price_combo = QtWidgets.QComboBox()
        self.execute_price_combo.addItems(
            [execute_price.value for execute_price in execute_prices])

        send_button = QtWidgets.QPushButton("发出")
        send_button.clicked.connect(self.send_condition_order)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(QtWidgets.QLabel(f"合约:{self.vt_symbol}"))
        hbox.addWidget(QtWidgets.QLabel("方向"))
        hbox.addWidget(self.direction_combo)
        hbox.addWidget(QtWidgets.QLabel("开平"))
        hbox.addWidget(self.offset_combo)
        hbox.addWidget(QtWidgets.QLabel("条件"))
        hbox.addWidget(self.condition_combo)
        hbox.addWidget(QtWidgets.QLabel("触发价"))
        hbox.addWidget(self.price_line)
        hbox.addWidget(self.price_check)
        hbox.addWidget(QtWidgets.QLabel("数量"))
        hbox.addWidget(self.volume_line)
        hbox.addWidget(QtWidgets.QLabel("执行价"))
        hbox.addWidget(self.execute_price_combo)

        hbox.addWidget(send_button)

        # Overall layout
        self.setLayout(hbox)

    def register_event(self) -> None:
        """"""
        self.signal_tick.connect(self.process_tick_event)
        self.event_engine.register(EVENT_TICK, self.signal_tick.emit)

    def process_tick_event(self, event: Event) -> None:
        """"""
        tick = event.data
        if tick.vt_symbol != self.vt_symbol:
            return

        if self.price_check.isChecked():
            self.price_line.setText(f"{tick.last_price}")

    def send_condition_order(self) -> bool:
        """
        Send new order manually.
        """
        try:
            direction = Direction(self.direction_combo.currentText())
            offset = Offset(self.offset_combo.currentText())
            condition = Condition(self.condition_combo.currentText())
            price = float(self.price_line.text())               
            volume = float(self.volume_line.text())
            execute_price = ExecutePrice(self.execute_price_combo.currentText())

            order = ConditionOrder(
                strategy_name = self.strategy.strategy_name,
                vt_symbol=self.vt_symbol,
                direction=direction,
                offset=offset,
                price=price,
                volume=volume,
                condition=condition,
                execute_price=execute_price
            )

            self.strategy.send_condition_order(order=order)

            print(f"发出条件单 : vt_symbol={self.vt_symbol},success ! {order}")
            return True

        except:
            print(f"发出条件单 : vt_symbol={self.vt_symbol},input error !")  
            return False

2.3 有条件单功能的CTA引擎——MyCtaEngine

2.3.1 MyCtaEngine的实现

在vnpy_ctastrategy\engine.py中对CtaEngine进行如下扩展:

class MyCtaEngine(CtaEngine):
    """  """

    condition_filename = "condition_order.json"     # 历史条件单存储文件

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

        self.condition_orders:Dict[str,ConditionOrder] = {}     # strategy_name: dict

    def load_active_condtion_orders(self):
        """  """
        return {}

    def process_tick_event(self,event:Event):
        """ 用tick的价格检查条件单 """
        super().process_tick_event(event)

        tick:TickData = event.data
        all_condition_orders = [order for order in self.condition_orders.values() \
            if order.vt_symbol == tick.vt_symbol and order.status == CondOrderStatus.WAITING]
        for order in all_condition_orders:
            # 检查条件单是否满足条件
            self.check_condition_order(order,tick)

    def check_condition_order(self,order:ConditionOrder,tick:TickData):
        """ 检查条件单是否满足条件 """       
        strategy = self.strategies.get(order.strategy_name,None)
        if not strategy or not strategy.trading:
            return False

        price = tick.last_price

        is_be = order.condition == Condition.BE and price >= order.price
        is_le = order.condition == Condition.LE and price <= order.price
        is_bt = order.condition == Condition.BT and price > order.price
        is_lt = order.condition == Condition.LT and price < order.price

        if is_be or is_le or is_bt or is_lt:
            # 满足触发条件
            if order.execute_price == ExecutePrice.MARKET:
                # 取市场价
                price = tick.last_price
            elif order.execute_price == ExecutePrice.EXTREME:
                # 取极限价
                price = tick.limit_up if order.direction == Direction.LONG else tick.limit_down
            else:
                # 取设定价
                price = order.price

            # 执行委托
            order_ids = strategy.send_order(
                    direction = order.direction,
                    offset=order.offset,
                    price=price,
                    volume=order.volume 
                )

            if order_ids:
                order.trigger_time = tick.datetime
                order.status = CondOrderStatus.TRIGGERED

                self.event_engine.put(Event(EVENT_CONDITION_ORDER,order))

    def send_condition_order(self,order:ConditionOrder):
        """  """
        strategy = self.strategies.get(order.strategy_name,None)
        if not strategy or not strategy.trading:
            return False

        if order.cond_orderid not in self.condition_orders:
            self.condition_orders[order.cond_orderid] = order
            self.event_engine.put(Event(EVENT_CONDITION_ORDER,order))
            return True

        return False

    def cancel_condition_order(self,cond_orderid:str):
        """  """
        order:ConditionOrder = self.condition_orders.get(cond_orderid,None)
        if not order:
            return False

        order.status = CondOrderStatus.CANCELLED
        self.event_engine.put(Event(EVENT_CONDITION_ORDER,order))
        return True

    def cancel_all_condition_orders(self,strategy_name:str):
        """  """
        for order in self.condition_orders.values():
            if order.strategy_name == strategy_name and order.status == CondOrderStatus.WAITING:
                order.status = CondOrderStatus.CANCELLED

                self.call_strategy_func(strategy,strategy.on_condition_order)
                self.event_engine.put(Event(EVENT_CONDITION_ORDER,order))

        return True

2.3.2 更换CtaEngine

对vnpy_ctastrategy__init__.py中的CtaTemplate进行如下修改:

from .engine import MyCtaEngine   # hxxjava add
class CtaStrategyApp(BaseApp):
    """"""

    app_name = APP_NAME
    app_module = __module__
    app_path = Path(__file__).parent
    display_name = "CTA策略"
    # engine_class = CtaEngine
    engine_class = MyCtaEngine    # hxxjava add
    widget_name = "CtaManager"
    icon_name = str(app_path.joinpath("ui", "cta.ico"))

2.3.3 对CtaTemplate进行扩展

对vnpy_ctastrategy\template.py中的CtaTemplate进行如下扩展:

    @virtual
    def on_condition_order(self, cond_order: ConditionOrder):
        """
        Callback of condition order update.
        """
        pass

    def send_condition_order(self,order:ConditionOrder):    # hxxjava add
        """ """
        if not self.trading:
            return False
        return self.cta_engine.send_condition_order(order)

    def cancel_condition_order(self,cond_orderid:str):      # hxxjava add
        """ """
        return self.cta_engine.cancel_condition_order(cond_orderid)

    def cancel_all_condition_orders(self):                  # hxxjava add
        """ """
        return self.cta_engine.cancel_all_condition_orders(self)

2.3.4 CTA用户策略中如何使用条件单功能

1)CTA策略中的条件单被触发点回调通知:

    def on_condition_order(self, cond_order: ConditionOrder):
        """
        Callback of condition order update.
        """
        print(f"条件单已经执行,cond_order = {cond_order}")

2)发起条件单

    cond_order = ConditionOrder(... ...)
    self.send_condition_order(cond_order)

3)取消条件单

    self.cancel_condition_order(cond_orderid)

4)取消策略的所有条件单

    self.cancel_all_condition_orders()

3. 条件单应该在vnpy系统中被广泛使用

  • 它应该运行在整个vnpy系统的底层,为各类的应用策略提供委托支持,
  • 对连续密集触发点条件单实施流控限制,避免交易接口出现丢单的流控问题
  • 各类应用的引擎应该提供send_condition_order()接口,实现条件单到不同应用委托执行逻辑
  • 各类应用的模板应该提供on_condition_order回调,解决条件单触发后对不同类型用户策略的触发通知
  • 用户策略尽量使用条件单来执行交易,避免直接执行来自接口的委托函数
Administrator
avatar
加入于:
帖子: 4550
声望: 325

感谢分享!

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

您好,请问
发起条件单
cond_order = ConditionOrder(... ...)
self.send_condition_order(cond_order)

这一部分的入参应该是什么样的?
可以作为on_trade()成交后设定条件单吗?

Member
avatar
加入于:
帖子: 420
声望: 181

aqua-pink wrote:

您好,请问
发起条件单
cond_order = ConditionOrder(... ...)
self.send_condition_order(cond_order)

这一部分的入参应该是什么样的?
可以作为on_trade()成交后设定条件单吗?

这里给出一个例子代码来参考,根据你自己的情况去修改:

    def execute(self,strategy:CtaTemplate):
        """ 
        一段将交易指令转化为条件单的例子 ,其中:
        self.price:开仓价
        self.stop_price:止盈价
        self.exit_price:止损价
        """
        if self.offset == Offset.OPEN:
            open_condition = Condition.LE if self.direction == Direction.LONG else Condition.BE
            stop_condition = Condition.BT if self.direction == Direction.LONG else Condition.LT
            exit_condition = Condition.LT if self.direction == Direction.LONG else Condition.BT
            # 止盈条件单
            stop_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.stop_price,volume=self.volume/2,condition=stop_condition)
            # 开仓条件单
            open_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.price,volume=self.volume,condition=open_condition)
            # 止损条件单
            exit_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.exit_price,volume=self.volume,condition=exit_condition)      

            for cond_order in [open_order,stop_order,exit_order]:
                result = strategy.send_condition_order(cond_order)
                print(f"{strategy.strategy_name}发送开仓条件单{'成功' if result else '成功'}:{cond_order}")

        elif self.offset == Offset.CLOSE:
            tj1 = self.direction == Direction.LONG and strategy.pos < 0
            tj2 = self.direction == Direction.SHORT and strategy.pos > 0
            if tj1 or tj2:
                exit_condition = Condition.LT if self.direction == Direction.LONG else Condition.BT
                exit_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                                    direction=self.direction,offset=self.offset,
                                    price=self.price,volume=abs(strategy.pos),condition=exit_condition,
                                    execute_price=ExecutePrice.MARKET)

                result = strategy.send_condition_order(exit_order)
                print(f"{strategy.strategy_name}发送平仓条件单{'成功' if result else '成功'}:{exit_order}")

影响条件单执行方式的主要参数:

  • condition,price —— 条件单触发点条件
  • direction,offset —— 委托方向和开平选择
  • excute_price —— 条件单被触发时的执行价格,设定价、市场价还是极限价
  • 极限价:买入为涨停价,卖出为跌停价

采用条件单的好处

  • 策略的全部委托在条件单管理器中都可以看到
  • 不像停止单,价格必须预先确定,条件单的执行价是随行就市的,你可以头一天发出条件单,执行价最后为第二天的涨停价。
Member
avatar
加入于:
帖子: 3
声望: 0

感谢分享!从中学到很多。
不过在以上代码中未发现有关TradingWidget定义的代码,望大佬分享。

Member
avatar
加入于:
帖子: 420
声望: 181

达哥242b4bb891d6461b wrote:

感谢分享!从中学到很多。
不过在以上代码中未发现有关TradingWidget定义的代码,望大佬分享。

答复:
有关TradingWidget定义的代码已经补上。

Member
avatar
加入于:
帖子: 126
声望: 14

优秀!hxx一直在增量的发布创造性内容。

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

hxxjava wrote:

aqua-pink wrote:

您好,请问
发起条件单
cond_order = ConditionOrder(... ...)
self.send_condition_order(cond_order)

这一部分的入参应该是什么样的?
可以作为on_trade()成交后设定条件单吗?

这里给出一个例子代码来参考,根据你自己的情况去修改:

    def execute(self,strategy:CtaTemplate):
        """ 
        一段将交易指令转化为条件单的例子 ,其中:
        self.price:开仓价
        self.stop_price:止盈价
        self.exit_price:止损价
        """
        if self.offset == Offset.OPEN:
            open_condition = Condition.LE if self.direction == Direction.LONG else Condition.BE
            stop_condition = Condition.BT if self.direction == Direction.LONG else Condition.LT
            exit_condition = Condition.LT if self.direction == Direction.LONG else Condition.BT
            # 开仓条件单
            stop_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.stop_price,volume=self.volume/2,condition=stop_condition)
            # 开仓条件单
            open_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.price,volume=self.volume,condition=open_condition)
            # 止损条件单
            exit_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                            direction=self.direction,offset=self.offset,
                            price=self.exit_price,volume=self.volume,condition=exit_condition)      

            for cond_order in [open_order,stop_order,exit_order]:
                result = strategy.send_condition_order(cond_order)
                print(f"{strategy.strategy_name}发送开仓条件单{'成功' if result else '成功'}:{cond_order}")

        elif self.offset == Offset.CLOSE:
            tj1 = self.direction == Direction.LONG and strategy.pos < 0
            tj2 = self.direction == Direction.SHORT and strategy.pos > 0
            if tj1 or tj2:
                exit_condition = Condition.LT if self.direction == Direction.LONG else Condition.BT
                exit_order = ConditionOrder(strategy_name=strategy.strategy_name,vt_symbol=self.vt_symbol,
                                    direction=self.direction,offset=self.offset,
                                    price=self.price,volume=abs(strategy.pos),condition=exit_condition,
                                    execute_price=ExecutePrice.MARKET)
                                       
                result = strategy.send_condition_order(exit_order)
                print(f"{strategy.strategy_name}发送平仓条件单{'成功' if result else '成功'}:{exit_order}")

影响条件单执行方式的主要参数:

  • condition,price —— 条件单触发点条件
  • direction,offset —— 委托方向和开平选择
  • excute_price —— 条件单被触发时的执行价格,设定价、市场价还是极限价
  • 极限价:买入为涨停价,卖出为跌停价

采用条件单的好处

  • 策略的全部委托在条件单管理器中都可以看到
  • 不像停止单,价格必须预先确定,条件单的执行价是随行就市的,你可以头一天发出条件单,执行价最后为第二天的涨停价。

非常感谢大佬的解释,我继续理解理解!感谢!

Member
avatar
加入于:
帖子: 26
声望: 1

感谢大佬分享,关于engine中的cancel_all_condition_orders方法

def cancel_all_condition_orders(self,strategy_name:str):
        """  """
        for order in self.condition_orders.values():
            if order.strategy_name == strategy_name and order.status == CondOrderStatus.WAITING:
                order.status = CondOrderStatus.CANCELLED

                self.call_strategy_func(strategy,strategy.on_condition_order)
                self.event_engine.put(Event(EVENT_CONDITION_ORDER,order))

        return True

其中有策略中on_condition_order的回调,并且未传入参数
正好最近刚应用了您编写的TickFilter(帖子:https://www.vnpy.com/forum/topic/30601-che-di-jie-jue-tickshu-ju-de-guo-lu-wen-ti?page=1)
其中也涉及到该部分的代码,是没有该动作的(self.call_strategy_func(strategy,strategy.on_condition_order)),应该是不需要的吧?

Member
avatar
加入于:
帖子: 420
声望: 181

是的,不需要

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

老师您好,实践上UI会缺少了右边的条件管理器窗口。是不是class CtaManager(QtWidgets.QWidget):部分的修改代码没贴上来的原因哦?

Member
avatar
加入于:
帖子: 420
声望: 181

少林寺猫猫 wrote:

老师您好,实践上UI会缺少了右边的条件管理器窗口。是不是class CtaManager(QtWidgets.QWidget):部分的修改代码没贴上来的原因哦?

不是的,相关的代码在StrategyManager中,一楼的帖子中有。

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

hxxjava wrote:

少林寺猫猫 wrote:

老师您好,实践上UI会缺少了右边的条件管理器窗口。是不是class CtaManager(QtWidgets.QWidget):部分的修改代码没贴上来的原因哦?

不是的,相关的代码在StrategyManager中,一楼的帖子中有。

实在抱歉,老师。我看了很久StrategyManager的代码,也测试过几次,都只是显示左侧的条件单管理器内容,而没有右侧的条件单管理器内容。我开始尝试在class CtaManager中参照停止单的写法,直接调用了ConditionOrderMonitor类,就显示了右侧的条件单管理器内容,但是部分功能会有异常报错。如双击撤单有问题等。

Member
avatar
加入于:
帖子: 420
声望: 181

少林寺猫猫 wrote:

hxxjava wrote:

少林寺猫猫 wrote:

老师您好,实践上UI会缺少了右边的条件管理器窗口。是不是class CtaManager(QtWidgets.QWidget):部分的修改代码没贴上来的原因哦?

不是的,相关的代码在StrategyManager中,一楼的帖子中有。

实在抱歉,老师。我看了很久StrategyManager的代码,也测试过几次,都只是显示左侧的条件单管理器内容,而没有右侧的条件单管理器内容。我开始尝试在class CtaManager中参照停止单的写法,直接调用了ConditionOrderMonitor类,就显示了右侧的条件单管理器内容,但是部分功能会有异常报错。如双击撤单有问题等。

CTA策略管理器的右侧共分三栏:停止单列表,条件单列表和 策略运行日志列表。
怎么会有左侧的呢?你截图看看。

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

hxxjava wrote:

少林寺猫猫 wrote:

hxxjava wrote:

少林寺猫猫 wrote:

老师您好,实践上UI会缺少了右边的条件管理器窗口。是不是class CtaManager(QtWidgets.QWidget):部分的修改代码没贴上来的原因哦?

不是的,相关的代码在StrategyManager中,一楼的帖子中有。

实在抱歉,老师。我看了很久StrategyManager的代码,也测试过几次,都只是显示左侧的条件单管理器内容,而没有右侧的条件单管理器内容。我开始尝试在class CtaManager中参照停止单的写法,直接调用了ConditionOrderMonitor类,就显示了右侧的条件单管理器内容,但是部分功能会有异常报错。如双击撤单有问题等。

CTA策略管理器的右侧共分三栏:停止单列表,条件单列表和 策略运行日志列表。
怎么会有左侧的呢?你截图看看。

抱歉,我表述不正确。应该说只可以显示交易工具条,而在CTA策略管理器中没有条件单列表。我看老师您贴上来的代码中,也没有对class CtaManager(CTA策略器)进行修改。
description

Member
avatar
加入于:
帖子: 420
声望: 181

你说的对,检查了一下,确实是遗漏了对class CtaManager修改的代码。
我已经修改了一楼的帖子,你查看下吧。

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

测试可用,感谢老师。

LLM学员
avatar
加入于:
帖子: 11
声望: 0

按上面的样子修改完代码后,条件单管理器没有出现

LLM学员
avatar
加入于:
帖子: 11
声望: 0

条件单回测是否必须使用tick数据

LLM学员
avatar
加入于:
帖子: 11
声望: 0

有没有使用停止单的完整策略可以参考一下,按上面修改了一直显示不出条件单管理器内容,不知道哪里有问题

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

沪公网安备 31011502017034号

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