Skip to content

tradingmachine.orders.plan_parts.lifetime

The lifetime of an order in a plan: when it stops, which part of its life that bounds, and what is done then.

A lifetime ends exactly one way: at_time, a time of day on the instrument's next trading day; after_minutes, counted from when the plan is placed; after_days, 1 to 365 days of 24 hours from then, which keeps the plan alive across trading days; or when, any trigger condition, checked on every tick. applies_to says whether the end bounds the wait for the trigger, the time the order works after it is sent, or both, which is UBI's default. An order still waiting when its end comes is done as expired. An order working then ends by on_end: cancel, UBI's default, cancels what rests and keeps what filled; marketable moves what rests two ticks past the other side's touch so it fills, which a stop cannot do; and close_filled cancels what rests and closes what filled at market, which only a plan that is this one order can do, and not an order that protects a position. A part ended by close_filled is done with the reason closed, or expired when nothing had filled.

Unlike the other parts, a lifetime is an entry of the order's lifetime list rather than a value under one key, so its document() holds the settings directly. A RepeatPart with until gives each copy a lifetime of its own, so an order repeated that way takes no Lifetime.

Typical usage example:

ending = lifetime.Lifetime(at_time="14:30", on_end="marketable") document = ending.document()

Lifetime

Lifetime(*, at_time=None, after_minutes=None, after_days=None, when=None, applies_to=None, on_end=None)

Bases: PlanPart

When an order of a plan stops working, and what is done with it then.

Attributes:

Name Type Description
at_time

The str time of day the order ends at, such as 14:30, or None.

after_minutes

The float minutes after placing that the order ends, or None.

after_days

The int days after placing that the order ends, from 1 to 365, or None.

when

The plan_part.PlanPart condition that ends the order when it holds, or None.

applies_to

The str part of the order's life the end bounds, waiting, working or both, or None for both.

on_end

The str action taken on a working order, cancel, marketable or close_filled, or None for cancel.

Initialises the lifetime with its end and settings.

Exactly one of at_time, after_minutes, after_days and when must be given; UBI refuses none or more than one.

Parameters:

Name Type Description Default
at_time str | None

The str time of day in India the order ends at, such as 14:30, on the instrument's next trading day, or None.

None
after_minutes float | None

The float minutes after the plan is placed that the order ends, above zero, or None. UBI refuses minutes on a day the instrument does not trade.

None
after_days int | None

The int days of 24 hours after the plan is placed that the order ends, from 1 to 365, or None.

None
when PlanPart | None

A plan_part.PlanPart trigger condition, such as PriceCrosses, TimeAt, AccountCondition or AnyCondition, that ends the order when it holds, or None.

None
applies_to str | None

The str part of the order's life the end bounds, waiting for the wait for its trigger, working for the time after it is sent, or both, or None for UBI's default of both.

None
on_end str | None

The str action taken on an order still working, cancel to cancel what rests, marketable to move what rests past the other side's touch, or close_filled to cancel what rests and close what filled at market, or None for UBI's default of cancel.

None
Source code in src/tradingmachine/orders/plan_parts/lifetime.py
def __init__(
    self,
    *,
    at_time: str | None = None,
    after_minutes: float | None = None,
    after_days: int | None = None,
    when: plan_part.PlanPart | None = None,
    applies_to: str | None = None,
    on_end: str | None = None,
):
    """Initialises the lifetime with its end and settings.

    Exactly one of `at_time`, `after_minutes`, `after_days` and `when` must be given; UBI refuses none or more than one.

    Args:
        at_time: The str time of day in India the order ends at, such as `14:30`, on the instrument's next trading day, or None.
        after_minutes: The float minutes after the plan is placed that the order ends, above zero, or None. UBI refuses minutes on a day the instrument does not trade.
        after_days: The int days of 24 hours after the plan is placed that the order ends, from 1 to 365, or None.
        when: A plan_part.PlanPart trigger condition, such as `PriceCrosses`, `TimeAt`, `AccountCondition` or `AnyCondition`, that ends the order when it holds, or None.
        applies_to: The str part of the order's life the end bounds, `waiting` for the wait for its trigger, `working` for the time after it is sent, or `both`, or None for UBI's default of `both`.
        on_end: The str action taken on an order still working, `cancel` to cancel what rests, `marketable` to move what rests past the other side's touch, or `close_filled` to cancel what rests and close what filled at market, or None for UBI's default of `cancel`.

    Raises:
        Nothing.
    """
    self.at_time = at_time
    self.after_minutes = after_minutes
    self.after_days = after_days
    self.when = when
    self.applies_to = applies_to
    self.on_end = on_end

document

document()

Builds the lifetime entry UBI reads, holding every setting that is not None.

Returns:

Type Description
dict

A dict holding each setting that is set, with when as its condition's object. It is one entry of the order's lifetime list, which OrderPart builds.

Examples:

Print a lifetime that ends at 14:30 and moves what still rests past the touch so it fills:

from tradingmachine.orders.plan_parts import lifetime

ending = lifetime.Lifetime(at_time="14:30", on_end="marketable")
print(ending.document())

Print an order whose wait ends when the price falls to 990 or when it is ten past three, whichever comes first:

from tradingmachine.orders.plan_parts import any_condition
from tradingmachine.orders.plan_parts import lifetime
from tradingmachine.orders.plan_parts import order_part
from tradingmachine.orders.plan_parts import price_crosses
from tradingmachine.orders.plan_parts import time_at

part = order_part.OrderPart(
    trigger=price_crosses.PriceCrosses(level=1010.0),
    lifetime=lifetime.Lifetime(
        when=any_condition.AnyCondition(
            [
                price_crosses.PriceCrosses(
                    level=990.0,
                    direction="at_or_below",
                ),
                time_at.TimeAt("15:10"),
            ]
        ),
        applies_to="waiting",
        on_end="cancel",
    ),
)
print(part.document())

Print an intraday entry that closes whatever filled thirty minutes after it is placed:

from tradingmachine.orders.plan_parts import lifetime
from tradingmachine.orders.plan_parts import order_part

part = order_part.OrderPart(
    lifetime=lifetime.Lifetime(
        after_minutes=30,
        on_end="close_filled",
    ),
)
print(part.document())
Source code in src/tradingmachine/orders/plan_parts/lifetime.py
def document(self) -> dict:
    """Builds the lifetime entry UBI reads, holding every setting that is not None.

    Returns:
        A dict holding each setting that is set, with `when` as its condition's object. It is one entry of the order's `lifetime` list, which `OrderPart` builds.

    Raises:
        Nothing.

    Examples:
        Print a lifetime that ends at 14:30 and moves what still rests past the touch so it fills:

        ```python
        from tradingmachine.orders.plan_parts import lifetime

        ending = lifetime.Lifetime(at_time="14:30", on_end="marketable")
        print(ending.document())
        ```

        Print an order whose wait ends when the price falls to 990 or when it is ten past three, whichever comes first:

        ```python
        from tradingmachine.orders.plan_parts import any_condition
        from tradingmachine.orders.plan_parts import lifetime
        from tradingmachine.orders.plan_parts import order_part
        from tradingmachine.orders.plan_parts import price_crosses
        from tradingmachine.orders.plan_parts import time_at

        part = order_part.OrderPart(
            trigger=price_crosses.PriceCrosses(level=1010.0),
            lifetime=lifetime.Lifetime(
                when=any_condition.AnyCondition(
                    [
                        price_crosses.PriceCrosses(
                            level=990.0,
                            direction="at_or_below",
                        ),
                        time_at.TimeAt("15:10"),
                    ]
                ),
                applies_to="waiting",
                on_end="cancel",
            ),
        )
        print(part.document())
        ```

        Print an intraday entry that closes whatever filled thirty minutes after it is placed:

        ```python
        from tradingmachine.orders.plan_parts import lifetime
        from tradingmachine.orders.plan_parts import order_part

        part = order_part.OrderPart(
            lifetime=lifetime.Lifetime(
                after_minutes=30,
                on_end="close_filled",
            ),
        )
        print(part.document())
        ```
    """
    settings = {}
    if self.at_time is not None:
        settings["at_time"] = self.at_time
    if self.after_minutes is not None:
        settings["after_minutes"] = self.after_minutes
    if self.after_days is not None:
        settings["after_days"] = self.after_days
    if self.when is not None:
        settings["when"] = self.when.document()
    if self.applies_to is not None:
        settings["applies_to"] = self.applies_to
    if self.on_end is not None:
        settings["on_end"] = self.on_end
    return settings

Example programs

Run it with .venv/bin/python examples/orders/plan_parts/lifetime/lifetime/three_ways_to_end_an_order.py.

"""Print one order ended each of the three ways UBI offers when its time comes.

A limit entry can be cancelled at 14:30, made marketable at 14:30 so it fills, or have whatever filled closed at market thirty minutes after placing. The program builds the same limit entry at 995 with each lifetime and prints all three. Nothing is sent to UBI's order routes.

Typical usage example:

  .venv/bin/python examples/orders/plan_parts/lifetime/lifetime/three_ways_to_end_an_order.py
"""

import json

from tradingmachine.orders.plan_parts import fixed_pricing
from tradingmachine.orders.plan_parts import lifetime
from tradingmachine.orders.plan_parts import order_part


class ThreeEndings:
    """The same limit entry with three different lifetimes.

    Attributes:
        lifetimes: The dict of lifetime.Lifetime objects by the str name of how each ends.
    """

    def __init__(self):
        """Builds the three lifetimes.

        Raises:
            Nothing.
        """
        self.lifetimes = {
            "cancel at 14:30": lifetime.Lifetime(at_time="14:30"),
            "marketable at 14:30": lifetime.Lifetime(
                at_time="14:30",
                on_end="marketable",
            ),
            "close what filled after 30 minutes": lifetime.Lifetime(
                after_minutes=30,
                on_end="close_filled",
            ),
        }

    def run(self) -> None:
        """Prints the entry's object with each lifetime.

        Returns:
            None.

        Raises:
            Nothing.
        """
        for name, ending in self.lifetimes.items():
            part = order_part.OrderPart(
                pricing=fixed_pricing.FixedPricing(price=995.0),
                lifetime=ending,
            )
            print(f"Ending by {name}:")
            print(json.dumps(part.document(), indent=2))


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

Build a dip entry whose wait ends if the index breaks down or the afternoon comes

Run it with .venv/bin/python examples/orders/plan_parts/lifetime/lifetime/wait_bounded_by_a_condition.py.

"""Build a dip entry whose wait ends if the index breaks down or the afternoon comes.

The program watches the NIFTY index and gives a dip entry on NSE IDEA a lifetime ending `when` either the index falls to 2 percent below its last value or it is 14:00, whichever comes first. The lifetime applies only to the wait, so an entry already sent is left to work. The market data read is read-only, and nothing is sent to UBI's order routes.

Typical usage example:

  .venv/bin/python examples/orders/plan_parts/lifetime/lifetime/wait_bounded_by_a_condition.py
"""

import json

from tradingmachine.assets import equities
from tradingmachine.orders.plan_parts import any_condition
from tradingmachine.orders.plan_parts import lifetime
from tradingmachine.orders.plan_parts import order_part
from tradingmachine.orders.plan_parts import price_crosses
from tradingmachine.orders.plan_parts import time_at


class BoundedDipEntry:
    """A dip entry whose wait ends on an index breakdown or at a time.

    Attributes:
        share: The equities.Equity bought on the dip.
        index: The equities.EquityIndex watched for a breakdown.
    """

    def __init__(self):
        """Looks the share and the index up.

        Raises:
            Nothing.
        """
        self.share = equities.Equity(exchange="nse", symbol="IDEA")
        self.index = equities.EquityIndex(exchange="nse", symbol="NIFTY")

    def run(self) -> None:
        """Prints the entry's object with the levels it uses.

        Returns:
            None.

        Raises:
            Nothing.
        """
        entry_level = round(self.share.last_price * 0.98, 2)
        breakdown_level = round(self.index.last_price * 0.98, 2)
        ending = lifetime.Lifetime(
            when=any_condition.AnyCondition(
                [
                    price_crosses.PriceCrosses(
                        level=breakdown_level,
                        direction="at_or_below",
                        instrument=self.index,
                    ),
                    time_at.TimeAt("14:00"),
                ]
            ),
            applies_to="waiting",
        )
        part = order_part.OrderPart(
            trigger=price_crosses.PriceCrosses(level=entry_level),
            lifetime=ending,
        )
        print(
            f"Entry at {entry_level}, given up if the index reaches {breakdown_level} or at 14:00:"
        )
        print(json.dumps(part.document(), indent=2))


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