Skip to content

tradingmachine.orders.iceberg

The iceberg synthetic order type: an order that rests one slice at a time and places the next when that slice fills.

Only one slice is ever visible, so the size of the whole order is hidden from the book. randomise_percent varies each slice so that the pattern is harder to spot. A randomised slice is brought to the nearest whole number of lots, at least one, and a slice cancelled or rejected at the broker ends the iceberg.

Typical usage example:

order = iceberg.IcebergOrder( share, transaction_type="buy", product="cnc", order_type="limit", quantity=1000, price=1000.0, slice_quantity=100, dry_run=True, ) answer = order.place()

IcebergOrder

IcebergOrder(instrument, *, transaction_type, product, order_type, quantity, slice_quantity, price=None, trigger_price=None, validity=None, disclosed_quantity=None, after_market=False, tag=None, price_reference=None, quantity_reference=None, closes_position=False, reduce_only=False, hold_limits=None, dry_run=False, randomise_percent=None)

Bases: SyntheticOrder

An order that rests one slice at a time and places the next when that slice fills.

Only one slice is ever visible, so the size of the whole order is hidden from the book. randomise_percent varies each slice so that the pattern is harder to spot. A randomised slice is brought to the nearest whole number of lots, at least one, and a slice cancelled or rejected at the broker ends the iceberg.

The order template's attributes are described on SyntheticOrder.

Attributes:

Name Type Description
slice_quantity

The int size of each slice, at least 1 and smaller than the quantity.

randomise_percent

The float percentage by which each slice may vary either way, at or above 0 and below 100, or None to let UBI use 0.

Initialises the order template and this type's own settings.

Parameters:

Name Type Description Default
instrument TradeableInstrument

The instruments.TradeableInstrument to place the order in.

required
transaction_type str

The str side of the order, buy or sell.

required
product str

The str product, cnc for delivery, mis for intraday or nrml for carry forward.

required
order_type str

The str kind of order, market, limit, sl or sl-m.

required
quantity int | None

The int quantity in underlying units, not lots, or None when a quantity reference supplies it.

required
slice_quantity int

The int size of each slice, at least 1 and smaller than the quantity.

required
price float | None

The float limit price in rupees, or None for an order type that takes no price or when a price reference supplies it.

None
trigger_price float | None

The float trigger price in rupees of the order itself, or None for an order type that takes no trigger.

None
validity str | None

The str validity, day or ioc, or None to let UBI use day.

None
disclosed_quantity int | None

The int quantity to show on the exchange, or None to disclose the whole order.

None
after_market bool

A bool that is True to send the order as an after-market order.

False
tag str | None

A str of up to twenty letters and digits to label the order with, or None.

None
price_reference dict | None

A dict describing the price for UBI to work out, such as {"kind": "mid"}, or None.

None
quantity_reference dict | None

A dict describing the quantity for UBI to work out, such as {"kind": "liquidate_position"}, or None.

None
closes_position bool

A bool that is True when every order this type sends closes a position, so it may use the share of a broker's daily order cap kept for exits.

False
reduce_only bool

A bool that is True to have UBI refuse, with HTTP 409, any leg that is not on the closing side of the net position held when it is sent or is bigger than that position.

False
hold_limits bool | None

A bool that is True to have UBI hold each order that would rest at the broker at a fixed limit price until the other side of the book reaches it, False to send them as they come, or None to let UBI use the type's default.

None
dry_run bool

A bool that is True to have UBI check the order and answer with the plan it would run, without recording or sending anything; the answer's request is the template as a broker would receive it, which for a stop is not the stop.

False
randomise_percent float | None

The float percentage by which each slice may vary either way, at or above 0 and below 100, or None to let UBI use 0.

None
Source code in src/tradingmachine/orders/iceberg.py
def __init__(
    self,
    instrument: instruments.TradeableInstrument,
    *,
    transaction_type: str,
    product: str,
    order_type: str,
    quantity: int | None,
    slice_quantity: int,
    price: float | None = None,
    trigger_price: float | None = None,
    validity: str | None = None,
    disclosed_quantity: int | None = None,
    after_market: bool = False,
    tag: str | None = None,
    price_reference: dict | None = None,
    quantity_reference: dict | None = None,
    closes_position: bool = False,
    reduce_only: bool = False,
    hold_limits: bool | None = None,
    dry_run: bool = False,
    randomise_percent: float | None = None,
):
    """Initialises the order template and this type's own settings.

    Args:
        instrument: The instruments.TradeableInstrument to place the order in.
        transaction_type: The str side of the order, `buy` or `sell`.
        product: The str product, `cnc` for delivery, `mis` for intraday or `nrml` for carry forward.
        order_type: The str kind of order, `market`, `limit`, `sl` or `sl-m`.
        quantity: The int quantity in underlying units, not lots, or None when a quantity reference supplies it.
        slice_quantity: The int size of each slice, at least 1 and smaller than the quantity.
        price: The float limit price in rupees, or None for an order type that takes no price or when a price reference supplies it.
        trigger_price: The float trigger price in rupees of the order itself, or None for an order type that takes no trigger.
        validity: The str validity, `day` or `ioc`, or None to let UBI use `day`.
        disclosed_quantity: The int quantity to show on the exchange, or None to disclose the whole order.
        after_market: A bool that is True to send the order as an after-market order.
        tag: A str of up to twenty letters and digits to label the order with, or None.
        price_reference: A dict describing the price for UBI to work out, such as `{"kind": "mid"}`, or None.
        quantity_reference: A dict describing the quantity for UBI to work out, such as `{"kind": "liquidate_position"}`, or None.
        closes_position: A bool that is True when every order this type sends closes a position, so it may use the share of a broker's daily order cap kept for exits.
        reduce_only: A bool that is True to have UBI refuse, with HTTP 409, any leg that is not on the closing side of the net position held when it is sent or is bigger than that position.
        hold_limits: A bool that is True to have UBI hold each order that would rest at the broker at a fixed limit price until the other side of the book reaches it, False to send them as they come, or None to let UBI use the type's default.
        dry_run: A bool that is True to have UBI check the order and answer with the `plan` it would run, without recording or sending anything; the answer's `request` is the template as a broker would receive it, which for a stop is not the stop.
        randomise_percent: The float percentage by which each slice may vary either way, at or above 0 and below 100, or None to let UBI use 0.

    Raises:
        Nothing.
    """
    super().__init__(
        instrument,
        transaction_type=transaction_type,
        product=product,
        order_type=order_type,
        quantity=quantity,
        price=price,
        trigger_price=trigger_price,
        validity=validity,
        disclosed_quantity=disclosed_quantity,
        after_market=after_market,
        tag=tag,
        price_reference=price_reference,
        quantity_reference=quantity_reference,
        closes_position=closes_position,
        reduce_only=reduce_only,
        hold_limits=hold_limits,
        dry_run=dry_run,
    )
    self.slice_quantity = slice_quantity
    self.randomise_percent = randomise_percent

synthetic_fields

synthetic_fields()

Gives this type's own settings, the fields of the synthetic object besides type.

Returns:

Type Description
dict

A dict of UBI field names to values, where a value of None means the field is left out.

Examples:

Print the settings of an order for ten shares shown two at a time:

from tradingmachine.assets import equities
from tradingmachine.orders import iceberg

share = equities.Equity(exchange="nse", symbol="IDEA")
order = iceberg.IcebergOrder(
    share,
    transaction_type="buy",
    product="mis",
    order_type="limit",
    quantity=10,
    price=13.0,
    slice_quantity=2,
)
print(order.synthetic_fields())

Work out how many slices an iceberg needs when each slice may vary by a fifth:

from tradingmachine.assets import equities
from tradingmachine.orders import iceberg

share = equities.Equity(exchange="nse", symbol="IDEA")
order = iceberg.IcebergOrder(
    share,
    transaction_type="sell",
    product="mis",
    order_type="limit",
    quantity=100,
    price=14.0,
    slice_quantity=15,
    randomise_percent=20,
)
fields = order.synthetic_fields()
slices = order.quantity // fields["slice_quantity"]
if order.quantity % fields["slice_quantity"]:
    slices = slices + 1
print(f"About {slices} slices, each within {fields['randomise_percent']}%")
Source code in src/tradingmachine/orders/iceberg.py
def synthetic_fields(self) -> dict:
    """Gives this type's own settings, the fields of the `synthetic` object besides `type`.

    Returns:
        A dict of UBI field names to values, where a value of None means the field is left out.

    Raises:
        Nothing.

    Examples:
        Print the settings of an order for ten shares shown two at a time:

        ```python
        from tradingmachine.assets import equities
        from tradingmachine.orders import iceberg

        share = equities.Equity(exchange="nse", symbol="IDEA")
        order = iceberg.IcebergOrder(
            share,
            transaction_type="buy",
            product="mis",
            order_type="limit",
            quantity=10,
            price=13.0,
            slice_quantity=2,
        )
        print(order.synthetic_fields())
        ```

        Work out how many slices an iceberg needs when each slice may vary by a fifth:

        ```python
        from tradingmachine.assets import equities
        from tradingmachine.orders import iceberg

        share = equities.Equity(exchange="nse", symbol="IDEA")
        order = iceberg.IcebergOrder(
            share,
            transaction_type="sell",
            product="mis",
            order_type="limit",
            quantity=100,
            price=14.0,
            slice_quantity=15,
            randomise_percent=20,
        )
        fields = order.synthetic_fields()
        slices = order.quantity // fields["slice_quantity"]
        if order.quantity % fields["slice_quantity"]:
            slices = slices + 1
        print(f"About {slices} slices, each within {fields['randomise_percent']}%")
        ```
    """
    return {
        "slice_quantity": self.slice_quantity,
        "randomise_percent": self.randomise_percent,
    }

Example programs

Preview and rest an iceberg offer whose slices vary in size

Run it with .venv/bin/python examples/orders/iceberg/iceberg_order/randomised_iceberg_offer.py.

"""Preview and rest an iceberg offer whose slices vary in size.

The program previews, then rests, an iceberg selling five Vodafone Idea shares 3% above the market in slices of about two, each allowed to vary by up to 40% so the pattern is harder to spot. It prints the parent and cancels it.

Typical usage example:

  .venv/bin/python examples/orders/iceberg/iceberg_order/randomised_iceberg_offer.py
"""

import time

from tradingmachine.accounts import account
from tradingmachine.assets import equities
from tradingmachine.assets import instruments
from tradingmachine.orders import iceberg
from tradingmachine.unified_broker_interface import exceptions


class RandomisedIcebergOffer:
    """An iceberg offer whose slice sizes are randomised.

    Attributes:
        trading_account: The tradingmachine.accounts.account.Account, used to read the engine's answer when it comes late.
        share: The tradingmachine.assets.equities.Equity for Vodafone Idea on the NSE.
        order: The tradingmachine.orders.iceberg.IcebergOrder the program places, or None before run() builds it.
    """

    def __init__(self):
        """Looks up the shares the program trades.

        Raises:
            tradingmachine.assets.exceptions.InstrumentError: A share could not be found in UBI.
        """
        self.trading_account = account.Account()
        self.share = equities.Equity(exchange="nse", symbol="IDEA")
        self.order = None

    def price_from_market(
        self,
        instrument: instruments.TradeableInstrument,
        percent: float,
    ) -> float:
        """Gives a price a percentage away from an instrument's last price, rounded to its tick size.

        Args:
            instrument: The tradingmachine.assets.instruments.TradeableInstrument to price.
            percent: The float percentage to move from the last price, negative for a price below the market.

        Returns:
            The float price in rupees.

        Raises:
            ValueError: UBI has no last price for the instrument.
        """
        last_price = instrument.last_price
        if last_price is None:
            raise ValueError(f"UBI has no last price for {instrument!r}")
        tick_size = 0.05
        if instrument.tick_size is not None:
            tick_size = float(instrument.tick_size)
        ticks = round(last_price * (1 + percent / 100) / tick_size)
        return round(ticks * tick_size, 2)

    def build_order(self, dry_run: bool) -> iceberg.IcebergOrder:
        """Builds an iceberg of five shares in slices of about two, 3% above the market.

        Args:
            dry_run: A bool that is True to build the order as a dry run, which UBI only checks and prices.

        Returns:
            The tradingmachine.orders.iceberg.IcebergOrder, not yet placed.

        Raises:
            ValueError: UBI has no last price for a share.
        """
        return iceberg.IcebergOrder(
            self.share,
            transaction_type="sell",
            product="mis",
            order_type="limit",
            quantity=5,
            price=self.price_from_market(self.share, 3),
            slice_quantity=2,
            randomise_percent=40,
            dry_run=dry_run,
        )

    def print_parent(self) -> None:
        """Prints the state the order engine holds the order in and each leg it has.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not read the parent.
        """
        parent = self.order.parent
        print(f"Parent state: {parent['state']}")
        for leg in parent["legs"]:
            print(
                f"  leg {leg.get('role')}: {leg.get('transaction_type')} "
                f"{leg.get('quantity')} at {leg.get('price')} "
                f"trigger {leg.get('trigger_price')}, {leg.get('state')}"
            )

    def place_order(self) -> dict:
        """Places the order, sending it again when a broker refuses it, and reading the engine's stored answer when the engine answers too late, so that the order can always be cancelled.

        Returns:
            The dict answer of the placement, whose `parent_id` is also kept on the order.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.OrderRejectedError: Brokers refused the order three times.
            tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError: The engine's answer could not be read within thirty seconds.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused the order or could not be reached.
        """
        for attempt in range(3):
            try:
                return self.order.place()
            except exceptions.OrderRejectedError as error:
                if attempt == 2:
                    raise
                print(f"A broker refused the order, so it is sent again: {error}")
                time.sleep(1)
            except exceptions.OrderOutcomeUnknownError as error:
                return self.read_late_answer(error)
        return {}

    def read_late_answer(
        self,
        error: exceptions.OrderOutcomeUnknownError,
    ) -> dict:
        """Reads the order engine's stored answer to a placement it answered too late, and keeps its parent id.

        Args:
            error: The tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError the placement raised.

        Returns:
            The dict answer the placement would have given.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError: The engine's answer could not be read within thirty seconds.
        """
        intent_id = error.detail.get("intent_id")
        print(f"The engine answered late, so intent {intent_id} is read.")
        for attempt in range(15):
            time.sleep(2)
            try:
                stored = self.trading_account.intent(intent_id)
            except exceptions.NotFoundError:
                continue
            answer = stored["response"]
            self.order.parent_id = answer.get("parent_id")
            return answer
        raise error

    def cancel_order(self) -> None:
        """Cancels the order in the order engine, with every leg it still has at a broker, and prints the result.

        A broker can refuse or be slow to answer one leg's cancel, which leaves the parent `cancelling`, and the engine itself can answer late, so the cancel is sent again until the parent is `cancelled`, up to five times.

        Returns:
            None.

        Raises:
            RuntimeError: The parent was still not cancelled after five attempts, so a leg may still be live.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused the cancel or could not be reached.
        """
        if self.order is None or self.order.parent_id is None:
            print("No order was placed, so there is nothing to cancel.")
            return
        for attempt in range(5):
            try:
                answer = self.order.cancel()
            except exceptions.ConflictError:
                print(f"The parent has already finished: {self.order.parent['state']}")
                return
            except exceptions.OrderOutcomeUnknownError:
                print("The engine answered the cancel late, so it is sent again.")
                time.sleep(3)
                continue
            print(
                f"Cancelled: state {answer['state']}, "
                f"{len(answer['cancelled_legs'])} legs cancelled"
            )
            for leg in answer["cancelled_legs"]:
                print(
                    f"  {leg.get('broker')} {leg.get('order_id')}: {leg.get('outcome')}"
                )
            if answer["state"] == "cancelled":
                return
            time.sleep(3)
        raise RuntimeError(
            f"Parent {self.order.parent_id} is still not cancelled, so check it by hand"
        )

    def run(self) -> None:
        """Previews the iceberg, places it, prints the parent and cancels it.

        Returns:
            None.

        Raises:
            ValueError: UBI has no last price for a share.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused a request or could not be reached.
        """
        preview = self.build_order(dry_run=True).place()
        print("A dry run, which sends and records nothing, says UBI would send:")
        print(preview.get("request", preview))
        self.order = self.build_order(dry_run=False)
        answer = self.place_order()
        print(
            f"Placed a {self.order.SYNTHETIC_TYPE} order: outcome "
            f"{answer.get('outcome')}, parent {self.order.parent_id}"
        )
        try:
            time.sleep(2)
            self.print_parent()
        finally:
            self.cancel_order()


if __name__ == "__main__":
    RandomisedIcebergOffer().run()

Rest an iceberg bid for two shares that shows one at a time

Run it with .venv/bin/python examples/orders/iceberg/iceberg_order/two_share_iceberg_bid.py.

"""Rest an iceberg bid for two shares that shows one at a time.

The program rests an iceberg buying two Vodafone Idea shares 3% below the market in slices of one, so only one share is ever visible. It prints the parent and the visible slice and cancels the iceberg.

Typical usage example:

  .venv/bin/python examples/orders/iceberg/iceberg_order/two_share_iceberg_bid.py
"""

import time

from tradingmachine.accounts import account
from tradingmachine.assets import equities
from tradingmachine.assets import instruments
from tradingmachine.orders import iceberg
from tradingmachine.unified_broker_interface import exceptions


class TwoShareIcebergBid:
    """An iceberg bid showing one share at a time.

    Attributes:
        trading_account: The tradingmachine.accounts.account.Account, used to read the engine's answer when it comes late.
        share: The tradingmachine.assets.equities.Equity for Vodafone Idea on the NSE.
        order: The tradingmachine.orders.iceberg.IcebergOrder the program places, or None before run() builds it.
    """

    def __init__(self):
        """Looks up the shares the program trades.

        Raises:
            tradingmachine.assets.exceptions.InstrumentError: A share could not be found in UBI.
        """
        self.trading_account = account.Account()
        self.share = equities.Equity(exchange="nse", symbol="IDEA")
        self.order = None

    def price_from_market(
        self,
        instrument: instruments.TradeableInstrument,
        percent: float,
    ) -> float:
        """Gives a price a percentage away from an instrument's last price, rounded to its tick size.

        Args:
            instrument: The tradingmachine.assets.instruments.TradeableInstrument to price.
            percent: The float percentage to move from the last price, negative for a price below the market.

        Returns:
            The float price in rupees.

        Raises:
            ValueError: UBI has no last price for the instrument.
        """
        last_price = instrument.last_price
        if last_price is None:
            raise ValueError(f"UBI has no last price for {instrument!r}")
        tick_size = 0.05
        if instrument.tick_size is not None:
            tick_size = float(instrument.tick_size)
        ticks = round(last_price * (1 + percent / 100) / tick_size)
        return round(ticks * tick_size, 2)

    def build_order(self, dry_run: bool) -> iceberg.IcebergOrder:
        """Builds an iceberg of two shares in slices of one, 3% below the market.

        Args:
            dry_run: A bool that is True to build the order as a dry run, which UBI only checks and prices.

        Returns:
            The tradingmachine.orders.iceberg.IcebergOrder, not yet placed.

        Raises:
            ValueError: UBI has no last price for a share.
        """
        return iceberg.IcebergOrder(
            self.share,
            transaction_type="buy",
            product="mis",
            order_type="limit",
            quantity=2,
            price=self.price_from_market(self.share, -3),
            slice_quantity=1,
            dry_run=dry_run,
        )

    def print_parent(self) -> None:
        """Prints the state the order engine holds the order in and each leg it has.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not read the parent.
        """
        parent = self.order.parent
        print(f"Parent state: {parent['state']}")
        for leg in parent["legs"]:
            print(
                f"  leg {leg.get('role')}: {leg.get('transaction_type')} "
                f"{leg.get('quantity')} at {leg.get('price')} "
                f"trigger {leg.get('trigger_price')}, {leg.get('state')}"
            )

    def print_broker_orders(self) -> None:
        """Prints the broker orders the order has placed so far.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not read the order book.
        """
        broker_orders = self.order.orders
        if broker_orders is None:
            print("No broker order has been placed yet.")
            return
        for row in broker_orders.to_dict("records"):
            print(
                f"  order {row.get('order_id')}: {row.get('transaction_type')} "
                f"{row.get('quantity')} at {row.get('price')}, {row.get('status')}"
            )

    def place_order(self) -> dict:
        """Places the order, sending it again when a broker refuses it, and reading the engine's stored answer when the engine answers too late, so that the order can always be cancelled.

        Returns:
            The dict answer of the placement, whose `parent_id` is also kept on the order.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.OrderRejectedError: Brokers refused the order three times.
            tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError: The engine's answer could not be read within thirty seconds.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused the order or could not be reached.
        """
        for attempt in range(3):
            try:
                return self.order.place()
            except exceptions.OrderRejectedError as error:
                if attempt == 2:
                    raise
                print(f"A broker refused the order, so it is sent again: {error}")
                time.sleep(1)
            except exceptions.OrderOutcomeUnknownError as error:
                return self.read_late_answer(error)
        return {}

    def read_late_answer(
        self,
        error: exceptions.OrderOutcomeUnknownError,
    ) -> dict:
        """Reads the order engine's stored answer to a placement it answered too late, and keeps its parent id.

        Args:
            error: The tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError the placement raised.

        Returns:
            The dict answer the placement would have given.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.OrderOutcomeUnknownError: The engine's answer could not be read within thirty seconds.
        """
        intent_id = error.detail.get("intent_id")
        print(f"The engine answered late, so intent {intent_id} is read.")
        for attempt in range(15):
            time.sleep(2)
            try:
                stored = self.trading_account.intent(intent_id)
            except exceptions.NotFoundError:
                continue
            answer = stored["response"]
            self.order.parent_id = answer.get("parent_id")
            return answer
        raise error

    def cancel_order(self) -> None:
        """Cancels the order in the order engine, with every leg it still has at a broker, and prints the result.

        A broker can refuse or be slow to answer one leg's cancel, which leaves the parent `cancelling`, and the engine itself can answer late, so the cancel is sent again until the parent is `cancelled`, up to five times.

        Returns:
            None.

        Raises:
            RuntimeError: The parent was still not cancelled after five attempts, so a leg may still be live.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused the cancel or could not be reached.
        """
        if self.order is None or self.order.parent_id is None:
            print("No order was placed, so there is nothing to cancel.")
            return
        for attempt in range(5):
            try:
                answer = self.order.cancel()
            except exceptions.ConflictError:
                print(f"The parent has already finished: {self.order.parent['state']}")
                return
            except exceptions.OrderOutcomeUnknownError:
                print("The engine answered the cancel late, so it is sent again.")
                time.sleep(3)
                continue
            print(
                f"Cancelled: state {answer['state']}, "
                f"{len(answer['cancelled_legs'])} legs cancelled"
            )
            for leg in answer["cancelled_legs"]:
                print(
                    f"  {leg.get('broker')} {leg.get('order_id')}: {leg.get('outcome')}"
                )
            if answer["state"] == "cancelled":
                return
            time.sleep(3)
        raise RuntimeError(
            f"Parent {self.order.parent_id} is still not cancelled, so check it by hand"
        )

    def run(self) -> None:
        """Places the iceberg, prints it and cancels it.

        Returns:
            None.

        Raises:
            ValueError: UBI has no last price for a share.
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused a request or could not be reached.
        """
        self.order = self.build_order(dry_run=False)
        answer = self.place_order()
        print(
            f"Placed a {self.order.SYNTHETIC_TYPE} order: outcome "
            f"{answer.get('outcome')}, parent {self.order.parent_id}"
        )
        try:
            time.sleep(2)
            self.print_parent()
            self.print_broker_orders()
        finally:
            self.cancel_order()


if __name__ == "__main__":
    TwoShareIcebergBid().run()