tradingmachine.asset_baskets.member_resolver
¶
Turns rows that name instruments into basket members with one list request to UBI.
A stored basket, a CSV file and the account's holdings all describe their instruments as plain rows, each naming an instrument by instrument_id or by exchange, segment and identity fields, beside a weight or a quantity. MemberResolver.resolve sends every row to POST /api/instruments/details at once and builds each member's instrument from its entry of the answer, so a basket of five hundred instruments costs one request rather than five hundred.
Typical usage example:
resolver = member_resolver.MemberResolver() members = resolver.resolve( [ {"exchange": "nse", "segment": "equities", "symbol": "INFY", "weight": 0.6}, {"exchange": "nse", "segment": "equities", "symbol": "TCS", "weight": 0.4}, ] )
MemberResolver
¶
A builder of basket members from rows that name their instruments.
Attributes:
| Name | Type | Description |
|---|---|---|
unified_broker_interface |
The client.UnifiedBrokerInterface the details request is sent through. |
Initialises the resolver 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. |
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/asset_baskets/member_resolver.py
resolve
¶
Looks every row's instrument up in UBI and returns one member per row.
An index becomes a tradingmachine.assets.instruments.NonTradeableInstrument and anything else a tradingmachine.assets.instruments.TradeableInstrument. A row that names its instrument by instrument_id is looked up by that alone, and any other row by the identity fields it has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict]
|
A list of dicts, each with |
required |
Returns:
| Type | Description |
|---|---|
list[BasketMember]
|
A list of basket_member.BasketMember, in the order of rows. |
Raises:
| Type | Description |
|---|---|
BasketMemberError
|
rows is empty, or UBI could not find one or more of the instruments, all of which the message lists. |
UnifiedBrokerInterfaceError
|
The whole request was refused by, or failed on the way to, UBI. |
Examples:
Build weighted members for three shares in one request:
from tradingmachine.asset_baskets import member_resolver
rows = [
{
"exchange": "nse",
"segment": "equities",
"symbol": "INFY",
"weight": 0.5,
},
{
"exchange": "nse",
"segment": "equities",
"symbol": "TCS",
"weight": 0.3,
},
{
"exchange": "nse",
"segment": "equity_indices",
"symbol": "NIFTY",
"weight": 0.2,
},
]
for member in member_resolver.MemberResolver().resolve(rows):
print(member, type(member.instrument).__name__)
See every unknown symbol listed in one error:
from tradingmachine.asset_baskets import exceptions
from tradingmachine.asset_baskets import member_resolver
rows = [
{
"exchange": "nse",
"segment": "equities",
"symbol": "INFY",
},
{
"exchange": "nse",
"segment": "equities",
"symbol": "NOSUCHSHARE",
},
]
try:
member_resolver.MemberResolver().resolve(rows)
except exceptions.BasketMemberError as error:
print(error)
Source code in src/tradingmachine/asset_baskets/member_resolver.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
resolve_one
¶
Looks one row's instrument up in UBI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
dict
|
A dict with |
required |
Returns:
| Type | Description |
|---|---|
Instrument
|
The tradingmachine.assets.instruments.NonTradeableInstrument for an index, or the tradingmachine.assets.instruments.TradeableInstrument for anything else. |
Raises:
| Type | Description |
|---|---|
BasketMemberError
|
UBI could not find the instrument. |
UnifiedBrokerInterfaceError
|
The request was refused by, or failed on the way to, UBI. |
Examples:
Look one share up by exchange, segment and symbol:
from tradingmachine.asset_baskets import member_resolver
resolver = member_resolver.MemberResolver()
idea = resolver.resolve_one(
{
"exchange": "nse",
"segment": "equities",
"symbol": "IDEA",
}
)
print(idea.instrument_id, idea.last_price)
Look an index up again by the instrument id another lookup gave:
from tradingmachine.asset_baskets import member_resolver
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
resolver = member_resolver.MemberResolver()
same_index = resolver.resolve_one(
{
"instrument_id": nifty.instrument_id,
}
)
print(type(same_index).__name__, same_index.symbol)
Source code in src/tradingmachine/asset_baskets/member_resolver.py
Example programs¶
Turn rows naming shares, a fund and an index into basket members with one request¶
Run it with .venv/bin/python examples/asset_baskets/member_resolver/member_resolver/resolve_mixed_rows.py.
"""Turn rows naming shares, a fund and an index into basket members with one request.
The program describes a small allocation as plain rows, the way a stored basket or a CSV file does: two shares and a gold fund by exchange, segment and symbol, and the NIFTY index by the instrument id a previous lookup gave. It resolves every row in one request to UBI and prints each member with the class of instrument it became and its last price.
Typical usage example:
.venv/bin/python examples/asset_baskets/member_resolver/member_resolver/resolve_mixed_rows.py
"""
from tradingmachine.asset_baskets import member_resolver
class ResolveMixedRows:
"""A resolver run over rows of several kinds.
Attributes:
resolver: The tradingmachine.asset_baskets.member_resolver.MemberResolver that looks the rows up.
"""
def __init__(self):
"""Creates the resolver on the shared UBI client.
Raises:
ValueError: The shared UBI client is not configured.
"""
self.resolver = member_resolver.MemberResolver()
def rows(self) -> list[dict]:
"""Builds the rows, looking the NIFTY index up first to name it by its id.
Returns:
A list of dicts, each naming one instrument and giving its weight.
Raises:
tradingmachine.asset_baskets.exceptions.BasketMemberError: UBI could not find the NIFTY index.
"""
nifty = self.resolver.resolve_one(
{
"exchange": "nse",
"segment": "equity_indices",
"symbol": "NIFTY",
}
)
return [
{
"exchange": "nse",
"segment": "equities",
"symbol": "INFY",
"weight": 40,
},
{
"exchange": "nse",
"segment": "equities",
"symbol": "HDFCBANK",
"weight": 30,
},
{
"exchange": "nse",
"segment": "exchange_traded_funds",
"symbol": "GOLDBEES",
"weight": 20,
},
{
"instrument_id": nifty.instrument_id,
"weight": 10,
},
]
def run(self) -> None:
"""Resolves the rows and prints each member.
Returns:
None.
Raises:
tradingmachine.asset_baskets.exceptions.BasketMemberError: UBI could not find one of the instruments.
tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused a request or could not be reached.
"""
members = self.resolver.resolve(self.rows())
for member in members:
kind = type(member.instrument).__name__
print(
f"{member.label:<16} {kind:<24} weight {member.weight:>3} last {member.instrument.last_price}"
)
if __name__ == "__main__":
ResolveMixedRows().run()
Resolve a list of symbols, report the ones UBI does not know, and keep the rest¶
Run it with .venv/bin/python examples/asset_baskets/member_resolver/member_resolver/skip_unknown_symbols.py.
"""Resolve a list of symbols, report the ones UBI does not know, and keep the rest.
The program tries to resolve a watch list typed by hand, which contains one misspelt symbol and one that was renamed. `MemberResolver.resolve` refuses the whole list and names every failure in one error, so the program then resolves each symbol on its own with `resolve_one`, keeps the ones UBI knows, and prints both groups.
Typical usage example:
.venv/bin/python examples/asset_baskets/member_resolver/member_resolver/skip_unknown_symbols.py
"""
from tradingmachine.asset_baskets import exceptions
from tradingmachine.asset_baskets import member_resolver
SYMBOLS = [
"INFY",
"TCS",
"INFOSYS",
"SBIN",
"HDFC",
]
class SkipUnknownSymbols:
"""A resolver run that separates the symbols UBI knows from the ones it does not.
Attributes:
resolver: The tradingmachine.asset_baskets.member_resolver.MemberResolver that looks the symbols up.
"""
def __init__(self):
"""Creates the resolver on the shared UBI client.
Raises:
ValueError: The shared UBI client is not configured.
"""
self.resolver = member_resolver.MemberResolver()
def row_for(self, symbol: str) -> dict:
"""Describes one NSE share as a row.
Args:
symbol: The str NSE symbol.
Returns:
A dict with `exchange`, `segment` and `symbol`.
Raises:
Nothing.
"""
return {
"exchange": "nse",
"segment": "equities",
"symbol": symbol,
}
def run(self) -> None:
"""Tries the whole list, then each symbol alone, and prints what was found.
Returns:
None.
Raises:
tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI refused a request or could not be reached.
"""
rows = []
for symbol in SYMBOLS:
rows.append(self.row_for(symbol))
try:
self.resolver.resolve(rows)
print("Every symbol is known.")
return
except exceptions.BasketMemberError as error:
print(f"The whole list was refused: {error}")
known = []
unknown = []
for symbol in SYMBOLS:
try:
instrument = self.resolver.resolve_one(self.row_for(symbol))
except exceptions.BasketMemberError:
unknown.append(symbol)
continue
known.append(instrument.symbol)
print(f"Known: {known}")
print(f"Unknown: {unknown}")
if __name__ == "__main__":
SkipUnknownSymbols().run()