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")
Account
¶
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
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 |
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 |
Raises:
| Type | Description |
|---|---|
BadRequestError
|
|
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
parents
property
¶
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 |
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:
intent
¶
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 |
required |
Returns:
| Type | Description |
|---|---|
dict
|
A dict 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
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()