For the Geeks
This page is for the moment when "the YAML looks right" is no longer enough. It explains the engine model underneath the fields: state machines, token registration, expression evaluation, order identity, OCA behavior,...
Written By Axiom Admin
Last updated 1 day ago
For The Geeks
This page is for the moment when "the YAML looks right" is no longer enough. It explains the engine model underneath the fields: state machines, token registration, expression evaluation, order identity, OCA behavior, and diagnostics.
It does not give you implementation code. It gives you enough of the model to predict behavior and debug without guessing.
The Engine In One Breath
On each bar, the Strategy Lab refreshes tokens, evaluates setup state, evaluates entry units, evaluates exit units, checks risk/runtime constraints, and then submits orders through TradingView's strategy engine.
The YAML does not become Pine code. It becomes structured configuration that the Strategy Lab interprets bar by bar.
Unit State Machines
Setups are state machines. They can be inactive, confirming, or active. ACTIVE is the final setup state for the current lifecycle: the setup context is live and entries or exits attached to it are allowed to evaluate.
For a setup named TREND_FILTER, watch:
If an entry belongs to TREND_FILTER, the entry does not get a real chance to fire until TREND_FILTER_ACTIVE is true.
Entries, take profits, and stop losses use the same language plus one extra state:
CONFIRMING means the gate and trigger are true, but the unit is still counting confirmation bars or ticks. WORKING means a limit, stop, or stop-limit order has been submitted but has not filled. ACTIVE means the unit has done the thing it was built to do: entry filled, take profit executed, or stop loss executed.
The best sanity test is small: one setup, one entry, one easy market exit. Confirm the setup state tokens first. Then add complexity.
Token Map
Every expression reads from the token map. Built-in prices, bar data, strategy stats, position state, custom indicator outputs, and generated state tokens are all just names in that map.
If a token does not exist, the engine reports a blocking diagnostic. If a token exists but currently has NaN, the expression may produce a warning or a false result depending on where that value flows.
That distinction matters:
Use Default Tokens for built-ins and Diagnostics & Error Codes when the parser tells you exactly which token or expression failed.
Expression Evaluation
Expressions compile into an internal form and then evaluate against the current token map. The expression parser knows about types, supported functions, history references, and field expectations.
A field that expects a boolean must end as true/false. A field that expects a price must end as a number. If an expression produces an error value or no usable value, the field may produce a warning first and then a blocking final-type error.
This is why you can see both messages for one expression:
ExampleWARN: Percent-change calculation needs a non-zero previous value. ERROR: Expression returned the wrong final type for this field.
The warning explains the cause. The error explains why the strategy cannot use the field's final result.
Warmup And Eager Function Inputs
Historical references need history. PRICE_CLOSE[5] does not exist on the first few bars. The parser can infer some warmup needs, but unsafe math still needs a fallback when you expect early bars to stay quiet.
This looks safe, but the function still receives an unavailable value:
Exampleentry_trigger_when:
BAR_INDEX > 5
&& PERCENT_CHANGE(PRICE_CLOSE, PRICE_CLOSE[5]) > 0.10This is safer because the fallback is inside the calculation:
Exampleentry_trigger_when:
BAR_INDEX > 5
&& SAFE_DIV(
PRICE_CLOSE - NZ(PRICE_CLOSE[5], PRICE_CLOSE),
NZ(PRICE_CLOSE[5], PRICE_CLOSE),
0
) * 100 > 0.10The lesson is unromantic but useful: if the dangerous value is inside a function call, guard the dangerous value before or inside that call.
Entry Identity
Entries use entry_name as their identity. That name becomes the order ID basis and the state-token base after sanitization.
There is no separate entry ID field in the current schema. If you want a clearer order name, name the entry clearly:
Example- entry_name: L_PULLBACK_50That gives you readable trade-list identity and tokens like:
Entry OCA
Entries can use OCA groups when orders compete with each other. The two fields are:
Exampleentry_oca_group: LONG_ENTRY_COMPETE
entry_oca_type: CANCELThis is for entry-side competition: breakout vs. pullback, first ladder fill vs. alternate ladder fill, and other cases where one entry order should affect another before or as it fills.
If there is no group, OCA type has no meaningful place to operate and the diagnostics will warn you.
Exit Model
Take profits and stop losses are independent units of work against the shared position for the direction.
They are not partitioned by entry. They do not require a from-entry field. They do not need to sum to 100 across the whole exit section.
Each exit asks a simpler question:
ExampleGiven the position that exists right now, should I try to reduce or close some of it?
That means these can both be valid:
Example- take_profit_name: TP_FIXED_TARGET
take_profit_gate_condition: POSITION_ACTIVE
take_profit_trigger_when: TRUE
take_profit_order_type: LIMIT
take_profit_limit_price: POSITION_AVERAGE_PRICE * 1.03
take_profit_allocation_percent: 100
- take_profit_name: TP_MOMENTUM_FAIL
take_profit_gate_condition: POSITION_ACTIVE
take_profit_trigger_when: CROSS_UNDER(RSI_K, 70)
take_profit_order_type: MARKET
take_profit_allocation_percent: 100They are alternative full-position exits. They are not a 200% allocation plan.
Exit OCA
Exit orders use reduce-style OCA behavior internally. That is the right default for one shared position because multiple working exits should compete to reduce the same remaining exposure without turning into accidental over-exits.
You do not configure exit OCA in YAML. The strategy handles it as part of the exit subsystem.
Use gates to decide which exits are eligible. Use allocation percentages to decide how much each eligible exit attempts to close.
Partial Exits And Remaining Position
When you want actual legs, use allocations below 100 and then gate later exits or stops with position state.
Example- take_profit_name: TP_HALF
take_profit_gate_condition: POSITION_ACTIVE
take_profit_trigger_when: TRUE
take_profit_order_type: LIMIT
take_profit_limit_price: POSITION_AVERAGE_PRICE * 1.02
take_profit_allocation_percent: 50
- stop_loss_name: SL_BREAK_EVEN
stop_loss_gate_condition:
POSITION_ACTIVE
&& POSITION_REMAINING_PERCENT <= 50
stop_loss_trigger_when: TRUE
stop_loss_order_type: STOP
stop_loss_stop_price: POSITION_AVERAGE_PRICE
stop_loss_allocation_percent: 100The remaining-position tokens are the bridge between "a TP should have reduced the position" and "now a different stop should be active."
Price Latching
*_lock_prices: true captures a price when the unit first triggers. The expression may continue to show a recalculated value in diagnostics, but the working order uses the latched price.
That can look confusing:
Examplestop_loss_stop_price: POSITION_AVERAGE_PRICE - ATR_14 * 2
stop_loss_lock_prices: trueThe diagnostic label may show today's recalculated POSITION_AVERAGE_PRICE - ATR_14 * 2. The order may still be sitting at the price captured when the stop first armed. That is normal.
Use locked prices for fixed targets and fixed risk. Leave prices unlocked when you intentionally want the order level to follow the current expression.
Diagnostics
The diagnostic layer exists because the engine now has more than one kind of failure:
The first diagnostic is often the most important one. Fix the earliest cause, reload, then see what remains.
If you hit a wall, make the strategy boring on purpose: one setup, one entry, one exit, one token. Get the machine moving. Then put the interesting parts back one at a time.