Build option recipes from a supplied chain¶
The Python builders select contracts from an OptionChain you supply and return
a signed composite recipe. They do not fetch a chain, calculate missing deltas,
certify complete data coverage or establish that the recipe can execute.
Import these legacy composite builders explicitly from atom.library.structures.
They are distinct from the typed Python bridge below and TradeScript's named
structure blocks; a similarly named family does not make a Composite executable.
Supply the decision date and selection inputs¶
Pass today=date(...) for pure construction. Missing decision dates now refuse
with ATOM-OPTION-CLOCK; the old fallback to the earliest expiry is removed.
The dispatch fallback uses the bar's UTC date. A naive timestamp means UTC;
an offset-aware timestamp is converted to UTC before taking its date. A declared
market timezone cannot change that instant or date. For example,
2026-01-02 00:30 UTC selects January 2, even though New York is still January 1.
Previously this fallback required a market timezone and could choose another day.
dte is a nonnegative integer number of calendar days from that date, default
45. Only expiries on or after the decision date and within dte - 7 through
dte + 7, inclusive, are considered. Candidates are searched by distance to
target DTE, then earlier expiry, until a complete requested geometry exists.
An expiry dated today is eligible for construction; this is not evidence that
its trading cutoff has not passed.
Delta recipes read supplied finite deltas in the appropriate call/put range.
Calls default to 0.16, puts to -0.16; condors use short_delta=0.16 for the
call and its negative for the put. Selection tolerance is inclusive 0.05.
Missing, nonfinite, wrong-sign or out-of-range deltas cannot supply a match.
Invalid selector values refuse with ATOM-OPTION-GEOMETRY. Ties use delta
distance, expiry, strike, then contract symbol (the supplied stable ID).
near_atm similarly orders by decimal strike distance, expiry, strike and symbol.
For spot 100.2 and equal-expiry strikes 100.1/100.3, it selects 100.1 first;
binary floating-point subtraction cannot break that mathematical tie.
Neither ordering depends on chain input order.
Contract IDs must be nonempty and unique, and every leg must belong to the
chain's stated underlying. Mixed-underlying or duplicate-ID chains refuse with
ATOM-OPTION-GEOMETRY rather than producing an ambiguous recipe.
Choose the geometry and direction¶
All single-expiry recipes require shared expiry; each leg uses its stated right.
ATM means the eligible anchor nearest the chain's finite positive spot, with
ties resolved by strike and symbol. Searches can use a farther anchor when the
closest one cannot supply a complete geometry. Widths are positive finite
strike-price points, not percentages, ticks or currency premiums. Widths
have no nearest-strike approximation or implicit tolerance.
| Builder | Default direction | Signed legs for that default |
|---|---|---|
straddle(chain, dte=45, today=...) |
short |
Sell call and put at the same strike nearest ATM |
strangle(chain, call_delta=0.16, put_delta=-0.16, ...) |
short |
Sell selected call and put; put strike must be below call strike |
iron_condor(chain, short_delta=0.16, wing_width=5, ...) |
short |
Sell inner call and put; buy call exactly wing_width above and put exactly that width below; inner put below inner call |
calendar_spread(chain, dte_front=30, dte_back=60, ...) |
long |
Sell front call, buy back call at the same strike; front expiry strictly before back expiry |
call_vertical(chain, width=..., ...) |
long |
Buy lower call, sell call exactly width higher |
put_vertical(chain, width=..., ...) |
long |
Buy higher put, sell put exactly width lower |
Every builder accepts direction='long' or direction='short'. Changing the
direction reverses every signed leg; it does not change selection. Calendars
search eligible front/back expiry pairs in front candidate order, then back
candidate order. They use calls only. Verticals require an explicit width and
choose the buy-side anchor nearest ATM for the long recipe.
No complete supplied geometry within these limits raises
ATOM-OPTION-NO-MATCH. Supply the missing exact contracts or deliberately
change the requested inputs; Atom will not silently widen the search. A custom
structure provider must also implement the separate runtime-checkable
VerticalStructureProvider protocol to support the vertical wrappers. A legacy
provider without both vertical methods refuses with ATOM-OPTION-GEOMETRY.
Worked pure-construction example¶
from datetime import date
from atom.helpers.options import Option, OptionChain
from atom.library.structures import call_vertical
expiry = date(2026, 2, 15)
chain = OptionChain(
underlying="XYZ", spot=100,
options=[
Option(symbol="XYZ_C100", underlying="XYZ", strike=100,
expiry=expiry, right="call"),
Option(symbol="XYZ_C105", underlying="XYZ", strike=105,
expiry=expiry, right="call"),
],
)
recipe = call_vertical(chain, width=5, dte=45, today=date(2026, 1, 1))
This constructs one unit of buy XYZ_C100 / sell XYZ_C105. It needs no delta
because strike geometry selects the legs. With direction='short', the signs
become sell XYZ_C100 / buy XYZ_C105. Requesting width 6 refuses because the
supplied chain lacks that exact pair.
This example deliberately stops at construction. The intended execution verb is
recipe.open(qty), preserving the recipe's signed direction; .sell() would
reverse it. Currently built-in recipes' .open(), .buy(), .sell() and .close() explicitly
refuse with ATOM-OPTION-TERMS before submission. The generic equity group path
cannot preserve option multipliers, per-leg terms, executable bid/ask quotes or
recipe lifetimes, so it is not a valid execution fallback. This also corrects
the previous builders' ability to submit option IDs as untyped equity legs.
Do not catch that refusal and submit the same legs as equities instead.
Point-width geometry and delta/ATM distances preserve the supplied decimal values independently of the host's numeric precision settings. For a 100.2/100.21 call chain, width 0.01 constructs that exact pair; another component lowering Decimal precision cannot remove it or admit an approximate pair. Positive OTM placement on the typed path below cannot round back to ATM.
Typed host handoff¶
Python strategies can also use the additive OptionDecision interface. This is
separate from the older Composite methods above: it sends a qualified intent
to an adopting host, not an equity order or a simulated option fill.
from atom.library.option_intents import OptionDecision, OptionDistance, OptionStructure
SPREAD = OptionStructure(
name="spread", kind="call_vertical",
width=OptionDistance(unit="points", value=5),
)
def on_tick():
decision = OptionDecision("XYZ", (SPREAD,), "recording@1")
view = decision.position("spread")
if view.is_flat:
decision.enter("spread", 2, "buy")
elif view.profit is not None and view.profit >= 0.5:
decision.close("spread")
The example requires an injected v1 host with the matching recording@1 policy,
an explicit strategy identity and a dated UTC decision/market clock. Naive decision
timestamps mean UTC; supplied host views still require aware, matching instants. It
is not runnable with Atom's default equity simulator. recording@1 names the
offline test policy, not a production admission or collateral policy.
With a covered XYZ chain containing 100/105 calls, two BUY verticals emit buy two 100 calls and sell two 105 calls. A multiplier of 100 stays attached as a contract term; it does not turn two contracts into 200 contracts. Named families require compatible multiplier, currency, exercise and settlement. Calendar expiries remain distinct. Unsupported adjusted or nonlinear terms refuse. Missing model identity refuses delta selection; a declared model name is a host assertion, not independent verification of the Greeks.
Placement accepts ATM, exact strike, delta magnitude, points OTM, percentage
points OTM (value=5 means 5%), or listed-strike steps. Widths in points need
the exact strike difference; strikes walks the actual listing grid. Expiry
defaults to 45 calendar days within an inclusive seven-day window. The snapshot
must cover the entire requested interval, even if a plausible contract exists.
Selection is deterministic and tries complete geometry, not approximate wings.
Typed named recipes also try the next ranked geometry when an anchor or wing
cannot accept the exact authored count. With one five-point call vertical and
100/105/110 listings, a three-contract lot step at 100 makes 105/110 the eligible
pair if those legs accept one. Counts and lot steps are never rounded; body ratios
apply once, so one butterfly needs two body contracts. No eligible recipe raises
ATOM-OPTION-NO-MATCH. This does not change the older Composite builder contract.
At a shared strike, the first lot-eligible contract ID wins; a rejected ID cannot
hide another eligible listing or change the underlying listed-strike grid.
The supplied snapshot declares the spot currency; selected strike currency must
match it. Same-day openings refuse in this version rather than infer a cutoff.
Custom legs allow explicit sides and ratios with open; their risk qualification
belongs to the host. Each typed resolution shares a 100,000-unit logical-work
budget across preprocessing, selector scans, candidate attempts and expiry
retries. Custom recursion also has a 10,000-node ceiling. An
ATOM-OPTION-SEARCH-LIMIT refusal is not a no-match result: narrow the requested
expiry/placement or supplied snapshot. Cached candidates are local to that one
resolution, never reused at a later decision. These bounds do not certify a
machine-independent wall-clock or memory limit.
The position view belongs to the exact strategy and recipe at the decision
instant. Missing profit/loss/DTE is unavailable, not a fabricated zero. Check
for None before a Python comparison, as the example does. Profit and
loss are nonnegative fractions supplied by the host under the pinned policy.
Closing reverses the held contract IDs, sides and counts with their lifetime
identity; it never rebuilds a spread from today's chain. Pending close permission
can suppress another close. This does not implement an order-lifetime ledger.
Each intent has a stable ID. The host must acknowledge and deduplicate repeats, including after restart, and reject conflicting payloads under one ID. Atom suppresses repeats only within a callback. A rejected or malformed acknowledgement is an essential error; earlier accepted intents are not rolled back. Quote fills, capital, reconciliation, exercise and production persistence remain outside Atom.
Integration work still pending¶
OptionChain has no full dated snapshot, coverage or contract-terms contract.
Construction does not prove freshness, coverage, dated multipliers and terms,
collateral admission, executable bid/ask prices, lifetime handling or expiry
settlement. The typed interface above requires a host to supply these views;
it does not upgrade an old OptionChain automatically. Production option
execution remains a separate integration. Named TradeScript structures
use the same typed decision interface, not the older Composite actions.
A returned composite handle alone
is not evidence of runnable option trading or financial acceptance.