Strategy events

StrategyEvent shape, emission points, parity corpus, and host serialization.

This page

Strategy events

Abstract

A strategy event is the immutable, structured record of one strategy.* action (or fill side-effect) at a specific bar. Events are the wire format between PYNE’s Python evaluator, PyneTS (StrategyEvent on RuntimeResult), and downstream HOOX trade workers (via pyne-worker). Changing the dataclass without updating the TS library and tests is a contract break.

Conceptual model

Diagram

Rendering…

Interface surface

StrategyEvent fields

Defined in src/pynescript/ast/evaluator/events.py:

FieldType (logical)Notes
kindentry | exit | close | close_all | cancel | cancel_all | orderDiscriminator
idstr | NoneOrder / entry id
directionlong | short | None
qtyfloat | None
order_typemarket | limit | stop | NoneStop-limit may appear as composite handling elsewhere
limit / stopfloat | NonePrices when relevant
oca_namestr | NoneOCA group
commentstr | NoneIncludes system comments (risk_blocked, fill:…)
bar_indexintFrom context at emit
bar_timeintFrom context
ohlc4-tuple → list in JSONBar snapshot
script_idstrHost-stamped (source hash)
run_idstrHost-stamped run uuid

Frozen dataclass → to_dict() always includes keys (JSON nulls for unspecified fields) so parity diffs are stable.

Emission sources

  1. Explicit builtinsstrategy.entry, exit, close, close_all, cancel, cancel_all, order push events as they execute.
  2. Broker fillsprocess_pending_orders emits fill-related order / entry / cancel (OCA) events.
  3. Risk gates — blocked entries may emit with comment="risk_blocked".

Host collection pattern

evaluator.reset_events()
# ... process_pending_orders + visit(tree)
for ev in evaluator._strategy_state.drain_events():
    d = ev.to_dict()
    d["script_id"] = script_id
    d["run_id"] = run_id
    all_events.append(d)

Runtime.run implements this and returns events in the response envelope.

Compile path

CompileStrategyBroker accumulates plain dict events (__events in the return mapping) with the same field names for kinds it supports. Exit/trail/OCA/risk halt comments (risk_blocked) are on both hosts for the 0.3.4+ surface; remaining metric accessors still trail the interpreter.

Internals

PathRole
src/pynescript/ast/evaluator/events.pyStrategyEvent
src/pynescript/ast/evaluator/builtins/strategy.pyEmit sites + drain_events
src/pynescript/compiler/strategy_broker.pyCompile-mode _emit
tests/test_strategy_events.pyUnit behavior
tests/test_parity.py + tests/fixtures/parity/Cross-port oracle
pynets/src/runtime/interpret.ts StrategyEventTS library twin

Parity corpus scripts (strategy_01_entry_long.pine, …) regenerate expected JSON via:

python tests/fixtures/parity/generate_fixtures.py

Tests strip script_id / run_id before compare.

Invariants & edge cases

  1. Immutability. Do not mutate events after construction; drain copies and clears the buffer.
  2. Per-bar reset. reset_events / drain prevents cross-bar leakage in tests that share evaluators.
  3. OHLC list vs tuple. to_dict converts to list for JSON round-trips.
  4. Kind vocabulary is closed. New kinds require TS + fixtures + this doc.
  5. Not alerts. alert() / alertcondition() use a separate alerts channel—not StrategyEvent.

Worked examples

Expected single entry

Script enters long on a fixed bar; fixture JSON lists one event:

{
  "kind": "entry",
  "id": "L",
  "direction": "long",
  "qty": 1.0,
  "order_type": "market",
  "bar_index": 5
}

(plus nullables and ohlc—see real fixtures for full keys).

Cancel after OCA

Fill of one OCA sibling yields cancel events for others with comment reflecting oca_cancel / oca_reduce.

Failure modes

SymptomCause
Empty events with live strategyForgot drain; or strategy never called
Parity flake on idsComparing script_id/run_id
TS/Python mismatchField rename without dual update
Duplicate eventsNot clearing buffer; double process_pending_orders

See also