Composite positions in Python¶
A composite groups multiple instrument legs into one position view. Use this advanced Python surface only when the execution environment supports the instruments and grouped operations.
pair("TCS", "INFY", ratio=2.0) describes a two-leg relationship. Buying one composite unit means one unit of TCS and two short units of INFY; selling reverses it.
basket(["TCS", "INFY"], [1.0, -1.0]) uses signed quantity multipliers, not money-allocation weights. Equal quantities do not mean equal notional exposure.
A handle provides buy, sell, close, is_flat and pnl. Repeated opening calls on an already-open handle are no-ops. Execution and partial-fill handling remain runtime concerns; grouping does not guarantee simultaneous broker fills.
Option structures require a supplied chain and additional lifecycle support. They are not enabled simply by mentioning a structure name. See composite reference for available Python functions. These functions do not make deferred TradeScript pair/options syntax executable.
Translate one composite unit into legs¶
| Handle and action | Desired leg quantities |
|---|---|
pair("TCS", "INFY", ratio=2).buy(3) |
Long 3 TCS, short 6 INFY. |
The same pair's .sell(3) |
Short 3 TCS, long 6 INFY. |
basket(["TCS", "INFY"], [1, -2]).buy(3) |
Long 3 TCS, short 6 INFY. |
The pair ratio and basket multipliers are quantities, not hedge estimates calculated from live prices. A two-to-one quantity relationship can have unequal money exposure. A basket needs at least one instrument and the same number of symbols and multipliers.
Manage the grouped lifecycle¶
A handle identifies a group. Default group identifiers are deterministic; use an explicit group_id when distinct strategies or ideas need separate groups. Create the handle as a descriptor, and call its actions inside strategy functions with valid execution context.
handle.is_flat checks the group, .close() requests exits for its actual legs, and .pnl reads grouped P&L. Repeated open calls while the group is already open are no-ops; use the actual grouped operation supported by your runtime rather than manually assuming atomic fills.
Partial leg execution can leave exposure different from the intended relationship. Instrument/shorting support, order limits and group tracking all matter. The option constructors in the reference additionally require a chain and supported expiry/strike contracts; they are advanced capability-dependent functions, not a beginner default.
Run a complete pair and basket¶
This Python strategy declares every required instrument, creates two independent groups, submits one entry for each and closes both after its exit condition. The thresholds are arithmetic teaching values, not a recommended market signal.
"""One-shot composite lifecycle tutorial, not an investment strategy."""
from atom.library.composite import basket, pair
symbols = ["TCS", "INFY", "RELIANCE", "HDFCBANK"]
spread = pair("TCS", "INFY", ratio=2.0, group_id="tutorial:pair")
hedge = basket(["RELIANCE", "HDFCBANK"], [1.0, -1.0], group_id="tutorial:basket")
def on_start():
return {}, {"opened": False}
def on_tick():
price = close["TCS"][0]
if not state.opened and price >= 100:
spread.buy(3)
hedge.buy(4)
state.opened = True
elif price < 100:
spread.close()
hedge.close()
The same maintained example is in
examples/python/critical_pair_basket.py in the repository.
symbols selects explicit multi-symbol access: on_tick() runs once for the
incoming panel, and close["TCS"][0] reads TCS deliberately. Do not replace
symbols with a flat universe here: that would broadcast the same function
per member, potentially submitting duplicate grouped entries before fills.
Supply aligned observations for all four symbols and an execution environment
that supports the short legs. Missing leg prices are not a hedge estimate.
When TCS first closes at or above 100, spread.buy(3) requests long 3 TCS and
short 6 INFY. hedge.buy(4) requests long 4 RELIANCE and short 4 HDFCBANK.
The pair ratio 2 multiplies units, while the basket's signed weights multiply
its four units. Neither recipe splits a money budget. They use distinct group
IDs so closing one cannot select the other's legs.
The one-shot state.opened flag prevents another entry request while prices
remain above the threshold and prevents re-entry after exit. It records an
attempt, not confirmed fills. The tutorial does not retry a rejection; inspect
rejected orders and each group's actual holdings before interpreting a run.
Once TCS is below 100, each handle's close() requests opposite quantities for
its actual open legs. Calling it on an already-flat group does nothing. Do not
use a bare single-symbol close() to mean close both groups.
For a small synthetic check, supply aligned panels priced 100, 101, 99, 99 for
all four symbols. After the first entry has filled, the expected groups are
{TCS: 3, INFY: -6} and {RELIANCE: 4, HDFCBANK: -4}. The below-100 signal
requests their exits; leave another bar for next-open execution. Both groups
finish flat in the local event backtest. These quantities and closure are
regression-tested against the maintained sample, not inferred from a syntax
check. This is not proof of simultaneous broker fills or live recovery.
The constructors default to one composite unit if no action quantity is given;
the example supplies quantities explicitly. risk_pct sizing on these handles
is unsupported and raises; use explicit units. Python composite support does
not enable the parked TradeScript pair/basket sizing forms. For a money-weighted
portfolio instead of unit recipes, use target portfolios.