Skip to content

tradingmachine.accounts.account

The whole trading account behind UBI, and the kill switch that empties it.

Account.flatten sends POST /api/orders/flatten, which stops every synthetic order UBI's order engine is running, cancels every open order at every broker, waits until the brokers confirm the cancellations, and only then closes every position with market orders. It acts on the whole account, not on one instrument; TradeableInstrument.liquidate_all_positions is the per-instrument equivalent. Account.parents lists every synthetic order the engine has not finished, and Account.intent reads the engine's answer to an order whose placement stopped waiting for it.

Typical usage example:

trading_account = account.Account() preview = trading_account.flatten(confirm="FLATTEN", dry_run=True) outcome = trading_account.flatten(confirm="FLATTEN")

FLATTEN_PATH module-attribute

FLATTEN_PATH = '/api/orders/flatten'

PARENTS_PATH module-attribute

PARENTS_PATH = '/api/orders/parents'

INTENT_PATH module-attribute

INTENT_PATH = '/api/orders/intents/{intent_id}'

DEFAULT_FLATTEN_TIMEOUT_SECONDS module-attribute

DEFAULT_FLATTEN_TIMEOUT_SECONDS = 120

Account

Account(unified_broker_interface=None)

The trading account UBI trades for, across every broker it is connected to.

Attributes:

Name Type Description
unified_broker_interface

The client.UnifiedBrokerInterface the account's requests are sent through.

Initialises the account with the client it sends requests through.

Parameters:

Name Type Description Default
unified_broker_interface UnifiedBrokerInterface | None

The client.UnifiedBrokerInterface to use, or None to share the one every instrument uses, which is almost always right because a second client would log the instruments out.

None

Raises:

Type Description
ValueError

No client was given and the shared client's base url or MongoDB credentials are not configured.

Source code in src/tradingmachine/accounts/account.py
def __init__(
    self,
    unified_broker_interface: client.UnifiedBrokerInterface | None = None,
):
    """Initialises the account with the client it sends requests through.

    Args:
        unified_broker_interface: The client.UnifiedBrokerInterface to use, or None to share the one every instrument uses, which is almost always right because a second client would log the instruments out.

    Raises:
        ValueError: No client was given and the shared client's base url or MongoDB credentials are not configured.
    """
    if unified_broker_interface is None:
        unified_broker_interface = (
            instruments.Instrument.shared_unified_broker_interface()
        )
    self.unified_broker_interface = unified_broker_interface

flatten

flatten(confirm, dry_run=False, timeout_seconds=DEFAULT_FLATTEN_TIMEOUT_SECONDS)

Stops every synthetic order, cancels every open order at every broker, then closes every position in the account.

UBI first halts every parent its order engine has not finished, so no armed trigger, trailing stop or grid can place anything afterwards; a halted parent's resting orders are left for the cancellations. The cancellations go next and the closes wait for them to be confirmed, because a stop or a target still resting when its position is closed would fill afterwards and open a new position the other way. Each position is then closed with a market order at the broker that holds it, all of them at once, and UBI waits again until the brokers' positions show zero before answering that the account is flat.

A timeout does not mean nothing happened: read the orders and positions before calling it again, or a second flatten may close positions twice.

Parameters:

Name Type Description Default
confirm str

The str FLATTEN, typed by the caller, which UBI requires so that a stray call cannot unwind the account.

required
dry_run bool

A bool that is True to report what would be cancelled and closed without sending anything.

False
timeout_seconds float

The float number of seconds to wait for UBI's answer, which takes one wait for the cancellations and one for the closed positions to show zero.

DEFAULT_FLATTEN_TIMEOUT_SECONDS

Returns:

Type Description
dict

For a dry run, a dict with dry_run, would_cancel and would_close. Otherwise a dict with halted, cancelled, still_open_after_waiting, closed, positions_still_open_after_waiting, flat and timing_ms, where flat is False when any part was not done or a closed position was still held when the wait ended, which UBI answers with HTTP 207 rather than as an error.

Raises:

Type Description
BadRequestError

confirm is not exactly FLATTEN.

ServiceUnavailableError

UBI could not read the order books or positions.

UnreachableError

No answer arrived within the timeout, so part of the flatten may have happened.

UnifiedBrokerInterfaceError

Any other failure reported by, or on the way to, UBI.

Examples:

Preview what a flatten would cancel and close, without sending anything:

from tradingmachine.accounts import account

trading_account = account.Account()
preview = trading_account.flatten(confirm="FLATTEN", dry_run=True)
print(f"Would cancel: {preview['would_cancel']}")
print(f"Would close: {preview['would_close']}")

See UBI refuse a flatten whose confirmation word is not typed exactly:

from tradingmachine.accounts import account
from tradingmachine.unified_broker_interface import exceptions

trading_account = account.Account()
try:
    trading_account.flatten(confirm="flatten", dry_run=True)
except exceptions.BadRequestError as error:
    print(f"Refused: {error.message}")

Flatten the whole account for real and check that it ended flat, which closes every position and cancels every order:

from tradingmachine.accounts import account

trading_account = account.Account()
outcome = trading_account.flatten(confirm="FLATTEN")
if outcome["flat"]:
    print(f"The account is flat after {outcome['timing_ms']} ms.")
else:
    print(outcome["still_open_after_waiting"])
    print(outcome["positions_still_open_after_waiting"])
Source code in src/tradingmachine/accounts/account.py
def flatten(
    self,
    confirm: str,
    dry_run: bool = False,
    timeout_seconds: float = DEFAULT_FLATTEN_TIMEOUT_SECONDS,
) -> dict:
    """Stops every synthetic order, cancels every open order at every broker, then closes every position in the account.

    UBI first halts every parent its order engine has not finished, so no armed trigger, trailing stop or grid can place anything afterwards; a halted parent's resting orders are left for the cancellations. The cancellations go next and the closes wait for them to be confirmed, because a stop or a target still resting when its position is closed would fill afterwards and open a new position the other way. Each position is then closed with a market order at the broker that holds it, all of them at once, and UBI waits again until the brokers' positions show zero before answering that the account is flat.

    A timeout does not mean nothing happened: read the orders and positions before calling it again, or a second flatten may close positions twice.

    Args:
        confirm: The str `FLATTEN`, typed by the caller, which UBI requires so that a stray call cannot unwind the account.
        dry_run: A bool that is True to report what would be cancelled and closed without sending anything.
        timeout_seconds: The float number of seconds to wait for UBI's answer, which takes one wait for the cancellations and one for the closed positions to show zero.

    Returns:
        For a dry run, a dict with `dry_run`, `would_cancel` and `would_close`. Otherwise a dict with `halted`, `cancelled`, `still_open_after_waiting`, `closed`, `positions_still_open_after_waiting`, `flat` and `timing_ms`, where `flat` is False when any part was not done or a closed position was still held when the wait ended, which UBI answers with HTTP 207 rather than as an error.

    Raises:
        BadRequestError: `confirm` is not exactly `FLATTEN`.
        ServiceUnavailableError: UBI could not read the order books or positions.
        UnreachableError: No answer arrived within the timeout, so part of the flatten may have happened.
        UnifiedBrokerInterfaceError: Any other failure reported by, or on the way to, UBI.

    Examples:
        Preview what a flatten would cancel and close, without sending anything:

        ```python
        from tradingmachine.accounts import account

        trading_account = account.Account()
        preview = trading_account.flatten(confirm="FLATTEN", dry_run=True)
        print(f"Would cancel: {preview['would_cancel']}")
        print(f"Would close: {preview['would_close']}")
        ```

        See UBI refuse a flatten whose confirmation word is not typed exactly:

        ```python
        from tradingmachine.accounts import account
        from tradingmachine.unified_broker_interface import exceptions

        trading_account = account.Account()
        try:
            trading_account.flatten(confirm="flatten", dry_run=True)
        except exceptions.BadRequestError as error:
            print(f"Refused: {error.message}")
        ```

        Flatten the whole account for real and check that it ended flat, which closes every position and cancels every order:

        ```python
        from tradingmachine.accounts import account

        trading_account = account.Account()
        outcome = trading_account.flatten(confirm="FLATTEN")
        if outcome["flat"]:
            print(f"The account is flat after {outcome['timing_ms']} ms.")
        else:
            print(outcome["still_open_after_waiting"])
            print(outcome["positions_still_open_after_waiting"])
        ```
    """
    body = {
        "confirm": confirm,
        "dry_run": bool(dry_run),
    }
    return self.unified_broker_interface.post(
        FLATTEN_PATH,
        body=body,
        timeout_seconds=timeout_seconds,
    )

parents property

parents

Every synthetic order and held order that UBI's order engine has not finished, in every instrument.

A parent is one order the engine was asked for, such as a bracket, a trailing stop or a limit order it is holding until the book reaches its price, and its legs are the broker orders it placed. TradeableInstrument.parents gives one instrument's.

Returns:

Type Description
DataFrame | None

A pandas.DataFrame with one row per parent, holding UBI's parent fields, among them parent_order_id, synthetic_type, state, instrument_id, body, parameters and legs, or None when no parent is open.

Raises:

Type Description
ServiceUnavailableError

UBI's parents could not be read.

UnifiedBrokerInterfaceError

Any other failure reported by, or on the way to, UBI.

Examples:

Print every parent the order engine is still working, or None when there is none:

from tradingmachine.accounts import account

trading_account = account.Account()
print(trading_account.parents)

Count the open parents of each synthetic order type:

from tradingmachine.accounts import account

trading_account = account.Account()
parents = trading_account.parents
if parents is None:
    print("No parent is open.")
else:
    print(parents["synthetic_type"].value_counts())

intent

intent(intent_id)

Reads what UBI's order engine did with one order after its placement stopped waiting for the answer.

Every answer to placing an order carries an intent_id, and so does the detail of an OrderOutcomeUnknownError raised when the engine did not answer in time. UBI keeps each answer for five minutes by default after the engine gives it.

Parameters:

Name Type Description Default
intent_id str

The str intent_id from the placement's answer or error detail.

required

Returns:

Type Description
dict

A dict with intent_id, the HTTP status the placement would have answered with, and the response body it would have answered with.

Raises:

Type Description
NotFoundError

The engine has not answered this intent yet, the id is not one, or its answer has expired.

ServiceUnavailableError

UBI could not read its store.

UnifiedBrokerInterfaceError

Any other failure reported by, or on the way to, UBI.

Examples:

Read the engine's stored answer to a held limit order by its intent_id, then cancel the order:

from tradingmachine.accounts import account
from tradingmachine.assets import equities

idea = equities.Equity(exchange="nse", symbol="IDEA")
price = round(idea.last_price * 0.97, 2)
answer = idea.buy_at_limit_price(price=price, quantity=1, product="mis")
try:
    trading_account = account.Account()
    engine_answer = trading_account.intent(answer["intent_id"])
    print(engine_answer["status"], engine_answer["response"]["outcome"])
finally:
    idea.cancel_parent(answer["parent_id"])

Handle an intent id the engine has no answer for:

from tradingmachine.accounts import account
from tradingmachine.unified_broker_interface import exceptions

trading_account = account.Account()
try:
    trading_account.intent("00000000-0000-0000-0000-000000000000")
except exceptions.NotFoundError as error:
    print(error.message)
Source code in src/tradingmachine/accounts/account.py
def intent(self, intent_id: str) -> dict:
    """Reads what UBI's order engine did with one order after its placement stopped waiting for the answer.

    Every answer to placing an order carries an `intent_id`, and so does the detail of an OrderOutcomeUnknownError raised when the engine did not answer in time. UBI keeps each answer for five minutes by default after the engine gives it.

    Args:
        intent_id: The str `intent_id` from the placement's answer or error detail.

    Returns:
        A dict with `intent_id`, the HTTP `status` the placement would have answered with, and the `response` body it would have answered with.

    Raises:
        NotFoundError: The engine has not answered this intent yet, the id is not one, or its answer has expired.
        ServiceUnavailableError: UBI could not read its store.
        UnifiedBrokerInterfaceError: Any other failure reported by, or on the way to, UBI.

    Examples:
        Read the engine's stored answer to a held limit order by its `intent_id`, then cancel the order:

        ```python
        from tradingmachine.accounts import account
        from tradingmachine.assets import equities

        idea = equities.Equity(exchange="nse", symbol="IDEA")
        price = round(idea.last_price * 0.97, 2)
        answer = idea.buy_at_limit_price(price=price, quantity=1, product="mis")
        try:
            trading_account = account.Account()
            engine_answer = trading_account.intent(answer["intent_id"])
            print(engine_answer["status"], engine_answer["response"]["outcome"])
        finally:
            idea.cancel_parent(answer["parent_id"])
        ```

        Handle an intent id the engine has no answer for:

        ```python
        from tradingmachine.accounts import account
        from tradingmachine.unified_broker_interface import exceptions

        trading_account = account.Account()
        try:
            trading_account.intent("00000000-0000-0000-0000-000000000000")
        except exceptions.NotFoundError as error:
            print(error.message)
        ```
    """
    return self.unified_broker_interface.get(
        INTENT_PATH.format(intent_id=intent_id),
    )

Example programs

Preview what the account-wide kill switch would cancel and close

Run it with .venv/bin/python examples/accounts/account/account/flatten_preview.py.

"""Preview what the account-wide kill switch would cancel and close.

The program asks UBI for a dry run of `Account.flatten`, which lists the open orders it would cancel and the positions it would close without sending anything, and prints a count of each followed by the details.

Typical usage example:

  .venv/bin/python examples/accounts/account/account/flatten_preview.py
"""

from tradingmachine.accounts import account


class FlattenPreview:
    """A dry run of flattening the whole trading account.

    Attributes:
        trading_account: The tradingmachine.accounts.account.Account the preview is asked for.
    """

    def __init__(self):
        """Creates the preview over the account UBI trades for.

        Raises:
            Nothing.
        """
        self.trading_account = account.Account()

    def print_items(self, heading: str, items: list) -> None:
        """Prints a heading with a count, then one line per item.

        Args:
            heading: The str heading, such as `Orders to cancel`.
            items: The list of items UBI reported, each printed as it came.

        Returns:
            None.

        Raises:
            Nothing.
        """
        print(f"{heading}: {len(items)}")
        for item in items:
            print(f"  {item}")

    def run(self) -> None:
        """Asks UBI for the dry run and prints what it would do.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused the request or could not be reached.
        """
        preview = self.trading_account.flatten(confirm="FLATTEN", dry_run=True)
        self.print_items("Orders to cancel", preview["would_cancel"])
        self.print_items("Positions to close", preview["would_close"])


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

Report the synthetic and held orders the order engine is working across the whole account

Run it with .venv/bin/python examples/accounts/account/account/open_parents_report.py.

"""Report the synthetic and held orders the order engine is working across the whole account.

The program reads the account's open parents once and prints how many there are of each synthetic order type and in each state.

Typical usage example:

  .venv/bin/python examples/accounts/account/account/open_parents_report.py
"""

from tradingmachine.accounts import account


class OpenParentsReport:
    """A report of the open parents in the whole trading account.

    Attributes:
        trading_account: The tradingmachine.accounts.account.Account the report reads.
    """

    def __init__(self):
        """Creates the report over the account UBI trades for.

        Raises:
            Nothing.
        """
        self.trading_account = account.Account()

    def run(self) -> None:
        """Reads the open parents and prints a count by type and by state.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not be reached or refused the request.
        """
        parents = self.trading_account.parents
        if parents is None:
            print("The order engine is not working any parent.")
            return
        print(f"Open parents: {len(parents)}")
        print(parents["synthetic_type"].value_counts())
        print(parents["state"].value_counts())


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

Look up the order engine's stored answer to an order by its intent id

Run it with .venv/bin/python examples/accounts/account/account/order_intent_lookup.py.

"""Look up the order engine's stored answer to an order by its intent id.

The program asks UBI's order engine to hold a buy limit order for one Vodafone Idea share about 3% below the last price, which does not fill, reads the engine's stored answer to that placement back through `Account.intent`, and cancels the held order before it ends, whatever happens.

Typical usage example:

  .venv/bin/python examples/accounts/account/account/order_intent_lookup.py
"""

from tradingmachine.accounts import account
from tradingmachine.assets import equities


class OrderIntentLookup:
    """A placement whose outcome is read back from the engine by its intent id.

    Attributes:
        trading_account: The tradingmachine.accounts.account.Account the intent is read through.
        idea: The tradingmachine.assets.equities.Equity for Vodafone Idea on the NSE, which the order is placed in.
    """

    def __init__(self):
        """Creates the account and looks the share up in UBI.

        Raises:
            tradingmachine.assets.exceptions.EquityError: UBI does not know the share.
        """
        self.trading_account = account.Account()
        self.idea = equities.Equity(exchange="nse", symbol="IDEA")

    def limit_price(self) -> float:
        """Works out a buy price about 3% below the last price, on the tick.

        Returns:
            The float limit price in rupees, rounded to the 0.01 tick.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not give the last price.
        """
        return round(self.idea.last_price * 0.97, 2)

    def run(self) -> None:
        """Places the held order, prints the engine's stored answer, and cancels the order.

        Returns:
            None.

        Raises:
            tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused a request or could not be reached.
        """
        price = self.limit_price()
        answer = self.idea.buy_at_limit_price(price=price, quantity=1, product="mis")
        print(f"Placed: outcome {answer['outcome']}, parent {answer['parent_id']}")
        try:
            engine_answer = self.trading_account.intent(answer["intent_id"])
            response = engine_answer["response"]
            print(f"Intent {engine_answer['intent_id']}")
            print(f"Stored HTTP status: {engine_answer['status']}")
            print(f"Stored outcome: {response['outcome']}")
            print(f"Stored parent id: {response['parent_id']}")
        finally:
            cancelled = self.idea.cancel_parent(answer["parent_id"])
            print(f"Cancelled: the parent is now {cancelled['state']}")


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