Объединенные ордера (Bracket Orders)

Общие вопросы

Еще такие ордера можно назвать скоординированными.

Скоординированные ордера предназначены для того, чтобы помочь ограничить ваши убытки и зафиксировать прибыль путем «объединения» ордера с двумя противоположными ордерами. Ордер на ПОКУПКУ состоит из лимитного ордера на продажу с высокой стороны и стоп-ордера на продажу с низкой стороны. Ордер на ПРОДАЖУ состоит из стоп-ордера на покупку с высокой стороны и лимитного ордера на покупку с низкой стороны. Обратите внимание, как брекет-ордера используют механизм прикрепления ордеров TWS API.

Bracket Orders are designed to help limit your loss and lock in a profit by "bracketing" an order with two opposite-side orders. A BUY order is bracketed by a high-side sell limit order and a low-side sell stop order. A SELL order is bracketed by a high-side buy stop order and a low side buy limit order. Note how bracket orders make use of the TWS API's Attaching Orders mechanism.

Одна ключевая вещь, которую следует иметь в виду, - это точно обрабатывать передачу ордеров. Поскольку объединенный ордер состоит из трех ордеров, всегда существует риск того, что хотя бы один из ордеров будет исполнен до того, как будет отправлена вся цепочка. Чтобы этого избежать, используйте флаг IBApi.Order.Transmit. Когда для этого флага установлено значение false, TWS будет получать ордер, но не будет отправлять (передавать) его на серверы. В приведенном ниже примере первый (родительский) и второй (takeProfit) ордера будут отправлены в TWS, но не переданы на серверы. Однако, когда отправляется последний дочерний ордер (stopLoss) и если его флаг IBApi.Order.Transmit установлен в true, TWS интерпретирует это как сигнал для передачи не только своего родительского ордера, но и остальных дочерних ордеров, удаляя риск случайного исполнения.

Дополнительная информация по объединенным ордерам:  


Полный текст примера: bracket_order_example_tws.py

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *
from threading import Timer

class TestApp(EWrapper, EClient):
    def __init__(self):
        EClient.__init__(self, self)

    def error(self, reqId , errorCode, errorString):
        print("Error: ", reqId, " ", errorCode, " ", errorString)

    def nextValidId(self, orderId ):
        self.nextOrderId = orderId
        self.start()

    def orderStatus(self, orderId , status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
        print("OrderStatus. Id: ", orderId, ", Status: ", status, ", Filled: ", filled, ", Remaining: ", remaining, ", LastFillPrice: ", lastFillPrice)

    def openOrder(self, orderId, contract, order, orderState):
        print("OpenOrder. ID:", orderId, contract.symbol, contract.secType, "@", contract.exchange, ":", order.action, order.orderType, order.totalQuantity, orderState.status)

    def execDetails(self, reqId, contract, execution):
        print("ExecDetails. ", reqId, contract.symbol, contract.secType, contract.currency, execution.execId,
              execution.orderId, execution.shares, execution.lastLiquidity)

    @staticmethod
    def BracketOrder(
        parentOrderId:int, 
        action:str,  # BUY or SELL
        quantity, # :Decimal
        limitPrice:float,
        delta : float
        ):
     
        #This will be our main or "parent" order
        parent = Order()
        parent.orderId = parentOrderId
        parent.action = action
        parent.orderType = "LMT"
        parent.totalQuantity = quantity
        parent.lmtPrice = limitPrice
        #The parent and children orders will need this attribute set to False to prevent accidental executions.
        #The LAST CHILD will have it set to True, 
        parent.transmit = False
        
        if action == "SELL": delta = -delta
        
        takeProfit = Order()
        takeProfit.orderId = parent.orderId + 1
        takeProfit.action = "SELL" if action == "BUY" else "BUY"
        takeProfit.orderType = "LMT"
        takeProfit.totalQuantity = quantity
        takeProfit.lmtPrice = limitPrice + delta
        takeProfit.parentId = parentOrderId
        takeProfit.transmit = False

        stopLoss = Order()
        stopLoss.orderId = parent.orderId + 2
        stopLoss.action = "SELL" if action == "BUY" else "BUY"
        stopLoss.orderType = "STP"
        #Stop trigger price
        stopLoss.auxPrice = limitPrice - delta/2
        stopLoss.totalQuantity = quantity
        stopLoss.parentId = parentOrderId
        #In this case, the low side order will be the last child being sent. Therefore, it needs to set this attribute to True 
        #to activate all its predecessors
        stopLoss.transmit = True

        bracketOrder = [parent, takeProfit, stopLoss]
        return bracketOrder

    def start(self):
        # define contract for USD.RUB forex pair
        FX_contract = Contract() 
        FX_contract.symbol = "USD"
        FX_contract.secType = "CASH"
        FX_contract.exchange = "IDEALPRO"
        FX_contract.currency = "RUB"

        
        bracket = self.BracketOrder(parentOrderId = self.nextOrderId, 
            action = "SELL", 
            quantity = 25000, 
            limitPrice = 77.67, 
             delta = 0.1)
        for order in bracket:
            self.placeOrder(order.orderId, FX_contract, order)
            #self.nextOrderId = self.nextValidId(self.nextOrderId)  # need to advance this we'll skip one extra oid, it's fine
        

    def stop(self):
        self.done = True
        self.disconnect()

def main():
    app = TestApp()
    app.nextOrderId = 0
    app.connect("127.0.0.1", 7497, 9)

    Timer(3, app.stop).start()
    app.run()

if __name__ == "__main__":
    main()