Engine host migration from Atom 4.x to 5.x¶
This note is for a downstream host such as strategy-engine-v2 that already runs Atom. Strategy authors should keep using protection and recovery and execution. Atom 5 does not restore 4.x compatibility shims; the host adapter and the Atom pin move together.
Worked numbers below use two owners on AAA: A long 2 at 100 and B short 2 at
200, with a 5% stop and 10% target. A's stop is 100 - 100*.05 = 95 and
target is 100 + 100*.10 = 110. B's short stop is 200 + 200*.05 = 210 and
target is 200 - 200*.10 = 180.
Replace rehydrate_protective_exits¶
atom.engine.stop_registry.rehydrate_protective_exits is removed. There is no
alias. The host passes confirmed inventory into
atom.engine.host_recovery.recover_fixed_protection and must not infer owners
from client internals. See atom/engine/host_recovery.py.
Removed 4.x call (do not restore this import):
from atom.engine.stop_registry import rehydrate_protective_exits
rehydrate_protective_exits(client, spec)
5.x replacement. Use the host registry's lifecycle. Pass every confirmed
(symbol, strategy_id, group_id) holding; a symbol net of zero is not a
substitute. On ProtectionStateError, refuse activation instead of starting
the strategy with partial protection:
from atom.engine.host_recovery import ConfirmedHolding, recover_fixed_protection
from atom.engine.order_lifecycle import ProtectionLifecycle, ProtectionStateError
lifecycle = ProtectionLifecycle() # Use the host registry's lifecycle in an integration.
holdings = [
ConfirmedHolding("AAA", 2, 100, strategy_id="A"),
ConfirmedHolding("AAA", -2, 200, strategy_id="B"),
]
spec = {"version": 1, "stop_pct": .05, "target_pct": .10, "non_rehydratable": []}
try:
recovered = recover_fixed_protection(lifecycle, holdings, spec)
except ProtectionStateError:
raise # Refuse activation; do not start with partial or inferred protection.
A second identical call returns [] and leaves levels 95/110 and 210/180
unchanged. Trailing anchors, ATR/points snapshots, timers and
stop_at/target_at need persisted entry-time state; an open holding with a
non-empty non_rehydratable list refuses the whole call.
Owner-keyed protection¶
Each (symbol, strategy_id, group_id) owns its quantity, entry price and
levels. Direct register_stop / register_target require a confirmed owner:
atom/engine/stop_registry.py calls resolve_owner(..., required=True), which
raises ProtectionStateError with no confirmed open position for owner when
that allocation is missing or flat (atom/engine/order_lifecycle.py). Reconcile
the host's confirmed quantity and average entry through
ProtectionLifecycle.reconcile(..., entry_price=) before those registrations.
A and B on the same symbol stay independent even when the net position is
zero. Do not register from a pending order or from symbol net inventory.
Live protection contract¶
Synchronous hosts that drive drive_stop_registry must set
protection_sync_version = 1 on the client
(atom/engine/stop_dispatch.py). The declaration alone is not proof of a
callback-isolation guard and is not permission to discard awaitables. After a
sync protective submit, Atom still requires confirmed closure or an exact
owned reservation; otherwise it raises ATOM-LIVE-PROTECTION: protective exit
lacks confirmed closure or an owned reservation.
Awaited AsyncLiveEngine hosts declare protection_exit_version = 1 and
implement persist_protection_state, submit_protection_exit and
protection_exit_status. Protected entry additionally needs
protection_entry_version = 1 and synchronous
_submit_protected_entry(symbol, delta, protection) -> bool. The names, payload
rules and restore order are in docs/internal/live-protection-contract.md and
the live protection examples.
TradeScript 5.x admission¶
TradeScript remains //@version=1. Condition strategies need both entry: and
exit: headers; omitting either is TS-SECTION-REQUIRED
(tradescript/validate/checks.py). Protection-only scripts may leave exit:
empty. hold: portfolios do not use those trading blocks.
A declared base timeframe is admitted only when the bar carries matching
bar.timeframe metadata (atom/library/readiness.py). Missing metadata is
TS-TF-COMPATIBILITY; Atom does not resample.
Metadata discovery and host admission are different. tradescript/strategy_config.py
reads recognised module-level names (symbol, symbols, universe,
SCHEDULE, SIZING_VERSION, PROTECTIVE_EXIT_SPEC, OPTION_INTENT_VERSION)
only as literals via ast.literal_eval. Non-literal assignments are ignored
there (non-literal <name> ignored) and do not fail admission. That path
does not inspect SIZE_SPEC.
The recorded Engine 26bbf17 rejection is
runner/lifecycle_manager.py::_validate_assignment, which raises
Top-level assignments must use literal values; move dynamic initialization
into on_start for a nonliteral unless it is one of the frozen compiler
forms that validator already allowlists (sizing = explicit_budget(...) /
vol_target(SIZE_SPEC, ...) / units_book(), constraints = [...], and a
fixed __builtins__ = dict(...) keyword set). Atom's compiler
(tradescript/lower/emit_py.py) still emits those constructors, and it also
emits readiness wrapping such as
on_tick = _with_readiness(on_tick, READINESS_SPEC, ...) (same wrap for
book / manage / shared_budget_signal). A host that requires every
top-level assignment, or every metadata name, to stay literal rejects that
output. Extend _validate_assignment to accept the current compiler forms
instead of forcing metadata to stay literal.
Pin Atom by commit SHA¶
Pin Atom to an immutable 40-character commit SHA. Do not resolve a branch name
such as main at deploy time. The Engine's 4.12 pin at 2b49c83 is the
migration window: move the pin in the same change as the adapter that imports
recover_fixed_protection.
tests/acceptance/test_engine_import_surface.py is the Atom-side contract for
that host. A failing pair means change the Engine pin or adapter in the same
release; do not loosen the test.
What a host upgrade must adopt¶
These Atom 5 contracts are intentional. They are the Engine 26bbf17 failure categories against 5.2.0, listed so a host can plan the work; they are not Atom defects and this package does not reintroduce 4.x behaviour.
- Call
recover_fixed_protectionwith host-confirmed holdings; do not importrehydrate_protective_exits. - Reconcile each owner's quantity and entry price before
register_stop/register_target. - Declare
protection_sync_version = 1on synchronous stop hosts, and still confirm closure or own a reservation after a protective submit. - Adopt the awaited protection names above when the host is async.
- Require
entry:andexit:on stored condition strategies. - Supply
bar.timeframeon admitted bars. - Adopt Engine
_validate_assignmentfor compiler__builtins__, sizing constructors and readiness wrapping; do not require every top-level assignment to be a literal. - Pin Atom by SHA, never by a floating branch.