All reference chapters

2. How a robot works

The engine does not execute a program you write; it runs a graph you declare. Market data enters at an adapter, an input turns every tick into a number, a pattern decides when that number means something, a rule turns the decision into a fully specified order, a robot owns the rules and is the thing you start and stop, and an account owns the robots together with everything they touch. This chapter walks that chain once, stage by stage, and explains what each stage is for and why it exists at all. It contains no code by design — the names of the calls appear where they belong to the explanation, and the dedicated chapters that follow give every stage its complete surface on all three languages.

Two properties hold along the whole chain. Each stage is declared under a unique label and is referred to by that label from the stage above — a pattern names its inputs, a rule names its pattern, a robot names its rules — so the graph is assembled bottom-up out of names rather than out of pointers. And declaring is not running: nothing in the graph touches data until the robot is started.

Market data arrives at an adapter

A market adapter is the single place where the outside world ends and the engine begins. It is a named, typed channel owned by the account: named, because inputs and portfolio subscriptions refer to it by that name; typed, because the record it accepts is fixed when it is created and is validated on every push. You may create as many adapters as you need — one per feed, per venue, per vendor, per resolution — and the same contract may live on several of them at once.

The five adapter types are the five shapes market data takes in this engine.

Adapter typeRecord pushedPush entry points
tse_md_ohlcvTseTickOHLCVtse_market_push_ohlcv_by_name, tse_market_push_ohlcv_by_id
tse_md_bidaskTseTickBidAsktse_market_push_bidask_by_name, tse_market_push_bidask_by_id
tse_md_tradeTseTickTradetse_market_push_trade_by_name, tse_market_push_trade_by_id
tse_md_executedTseRetainedtse_market_push_executed_by_name, tse_market_push_executed_by_id
tse_md_bookTseBookMessagetse_market_push_book_by_name, tse_market_push_book_by_id

Data is fed tick by tick, and each push addresses exactly one contract — either by its symbol, or by the numeric uint64_t contract id returned by tse_add_contract_id and tse_get_contract_id. Addressing by id costs no lookup and is what a high-rate replay should use. A contract becomes known to an adapter through wiring only: by binding an input to it, or by subscribing it with tse_portfolio_subscribe. There is no standalone call that adds a contract to an adapter, and pushing to a contract the adapter does not carry is an error.

Two of the five types are pure input sources. The executed feed carries trades that happened elsewhere, and the book feed carries order-book messages; neither offers a price the portfolio could mark a position against, nor a venue a simulated fill could occur on. A simulator therefore cannot be attached to them, and a portfolio subscription on them is refused with a diagnostic. An adapter is opened with tse_market_create, which is given the account, the label the rest of the graph will refer to it by, and the TseMdType that fixes its record shape for good. The order-book chapter covers the book feed in full; the environment chapter covers the loaders that turn CSV files into tick arrays ready to be pushed.

An Input processes every tick

An input is the entry node of the graph and the one place in the whole pipeline where your own ideas live. It is a named, duration-stamped cache built over a set of contracts on one adapter: raw ticks arrive at the adapter, the input hands each tick to your data processor, and whatever the processor stores becomes the series that patterns later observe.

What crosses the boundary is deliberately minimal. The processor receives the input's storage handle, the contract the tick belongs to, the tick itself and the opaque userData pointer it was registered with. It may push a timestamped value into the storage, and it returns a readiness flag — non-zero once the input has enough material to be observed.

The engine never inspects the callable — it holds a function pointer of a fixed signature and a pointer it does not interpret, and it calls them. A three-line moving average, a gradient-boosted model, a neural network scoring news sentiment and a call out to an external service are consequently the same object to the engine, and swapping one for another changes no wiring at all. The builders tse_add_input_ohlcv, tse_add_input_bidask, tse_add_input_trade and tse_add_input_executed cover the four tick feeds; tse_add_input_book and tse_add_input_book_imbalance cover the book feed. Each takes the adapter it reads from, the list of contract symbols it covers — binding registers those contracts on the adapter — a cache length, an explicit duration and a core id. The callable itself has one type per feed — TseOhlcvInputProcessor for candles, and its siblings for the other four — and it stores a value by calling tse_storage_push with a timestamp and a number. The Input chapter details every argument and gives each signature.

A Pattern watches Inputs and fires

A pattern is the decision node. It observes one or more inputs by label and emits a signal whenever its condition holds, carrying the timestamp of the input update that produced it. A pattern produces nothing tradeable by itself: a signal becomes an order only through a rule that names the pattern.

Six kinds cover the vocabulary of a condition, and each demands an exact number of observed inputs.

  • tse_add_pattern_threshold — one input: fires while the value compares against a constant as asked.
  • tse_add_pattern_peak — one input: fires when the series forms a local peak.
  • tse_add_pattern_timestamp — one input: fires once the input timestamp reaches a checkpoint, then every cool-down thereafter.
  • tse_add_pattern_comparison — two inputs: fires while one compares against the other at the same timestamp.
  • tse_add_pattern_crossover — two inputs: fires on the bar where that comparison flips from false to true.
  • tse_add_pattern_formula — any number of inputs: your callback receives each update and decides.

The comparison itself is a TseCmp value — tse_cmp_ge, tse_cmp_lt, tse_cmp_gt, tse_cmp_le, tse_cmp_eq, tse_cmp_ne. Every builder takes an explicit TseDuration: the engine never derives a pattern's duration from the inputs it observes, exactly as it never derives an input's own. Every builder also takes a mandatory core id, which pins the pattern's worker thread or asks for no separate thread at all. The Pattern chapter gives each kind its exact firing semantics.

A Rule turns a firing into an order

A rule is the bridge between observation and action. Everything the resulting order will carry is fixed when the rule is built — the transaction side, the position side that must hold, the quantity mode and quantity, the price form and limit price, slippage, fee, time-in-force and the rule's priority among the observers of the same pattern. Those ten values travel together in TseRuleParams, and none of them has a library-supplied default. The contract is not one of them: it is named separately, as the trailing contractSymbol argument of the builder. The firing supplies only the moment; in the single mode tse_quantity_from_signal it also supplies the quantity.

  • tse_add_rule_market — the market channel proper: an entry, an exit or a rebalance, selected by TseRuleType and bound to a pattern.
  • tse_add_rule_risk — the stop-loss and take-profit families, fixed or trailing. This rule takes no pattern at all: it watches the position and fires when the unrealized-P&L ratio crosses its threshold.
  • tse_add_rule_multileg, tse_add_rule_bracket, tse_add_rule_oco — several legs emitted as one atomic transaction, an entry with venue-resting protective legs, and a one-cancels-other pair.
  • tse_add_rule_cancel, tse_add_rule_replace, tse_add_rule_modify — amendments of an order already in flight.

A rule decides what to send; it does not decide what is permitted. Permission is a separate layer: risk policies declared with tse_add_risk_policy and tse_add_risk_policy_time_period gate orders on position value, on position quantity and on the trading window before anything reaches the execution. The risk management chapter treats that layer on its own, and the Rule chapter documents every field of TseRuleParams.

A Robot owns a set of Rules

A robot is the execution node that closes the graph at the top, and the unit at which the engine is operated. It is declared over rules that already exist, it receives every instruction those rules emit, and it forwards each one into the account's order management under its own label. Because that label travels onward with every reservation, fill and journaled trade, results can be read back per robot on an account running many robots at once.

A robot is declared with tse_add_robot, which is handed the labels of the rules it is to own. Since a rule names its pattern and a pattern names its inputs, adding a robot transitively takes in a whole dependency component whose leaves are inputs. Wiring alone changes nothing: tse_start is what sets the component in motion, tse_stop and tse_halt bring it to rest, and the bulk actions tse_cancel_all, tse_sale_all, tse_halt_and_cancel_all, tse_halt_and_sale_all and tse_halt_and_cancel_sale_all combine halting with withdrawing resting orders and flattening positions.

The robot is also the unit the licence counts. Starting one takes a seat keyed by the account and the robot label; stopping or halting it returns that seat. Which pool the seat comes from is declared per account with tse_account_set_modetse_mode_backtest or tse_mode_live — and the applied ceilings and observed peaks are readable at any time through tse_protection_status.

An Account owns the robots

The account is the root of every run. It is created first with tse_account_create — which asks for a label, a storage regime, a currency and a core id, and supplies no default for any of them — everything else is declared on it, and every result is read back through it.

The account owns the contracts declared with tse_add_contract, the market adapters, the single execution, the books, the portfolio that carries the positions, and the blotter that journals the trades — the storage regime chosen at creation selects whether that blotter lives in memory, in a database, or in both. Handles taken from an account are borrowed views into it, so the account is destroyed last.

The execution is the account's outbound edge, and there is exactly one of it. tse_exec_create_simulator builds the engine's own fill model, which is connected to market data and fills orders as ticks arrive. tse_exec_create_custom builds an execution that is not connected to market data at all: it hands each order to your callback, which reports fills back with tse_exec_apply_fill. Swapping one for the other is the whole distance between a backtest and live trading — the graph above it does not change.

The declaration order follows the dependency order.

  • Create the account, then declare its contracts.
  • Create the market adapters the data will arrive on.
  • Create the execution, simulated or custom.
  • Declare the inputs, naming their adapter and contracts.
  • Declare the patterns over the input labels.
  • Declare the rules over the pattern labels, and the risk policies that gate them.
  • Declare the robot over the rule labels.
  • Start the robot, then push data.

Every contract a running robot trades is marked to market automatically; tse_portfolio_subscribe adds further contracts you want marked without trading them. The Account chapter covers the portfolio, the booking calls and the resets in full.

What comes back

Fills return along the same chain in the opposite direction. Each one moves the portfolio, so the position and the account aggregate are current at any instant: tse_get_position_state snapshots one contract into TsePositionState, tse_get_portfolio_state snapshots the whole book into TsePortfolioState.

Each fill is also journaled by the blotter as a retained trade. tse_get_trades reads them back as TseTrade records, filtered by time range and optionally by robot. A retained trade is deliberately wide: besides price, quantity, fee and booked P&L it carries both order identifiers, the rule and robot labels that produced it, and the contract and portfolio exposure captured at the moment of the trade — which is what makes the journal joinable against whatever your own processor logged.

Aggregates come from the same store. tse_get_summary folds the whole account into a TseSummary; tse_get_robot_summary and tse_get_summaries do the same per robot. Beyond the summary lies the ex-post layer, which buckets each robot's trades over a time step and scores them: tse_ex_post_save writes the two-table SQLite database, while tse_ex_post_create keeps a live object whose feature matrices are read through tse_ex_post_feature_momentum, tse_ex_post_feature_ewma and tse_ex_post_feature_level_crossings — the projections a model uses to choose among strategy candidates. Trades executed outside the engine can be folded into the same picture with tse_book_trade and tse_book_trade_with_exposure.

The whole assembly at a glance

Engine architecture: market data, model output and derived series are pushed into a named market adapter; inside the account, which owns the contracts, the adapters, the execution and the robots, the chain runs Input, then Pattern, then Rule, then Execution; a robot is a named set of rules; pre-trade risk is computed at the rule and bracket and OCO orders are left at the venue; ExPost reports the summary, every retained trade and the duration-bucketed scores.

Every robot is assembled by the same twelve declarations, in the same order, and none of them is optional. The account is created; an adapter is opened for the shape of market data that will arrive; an execution is attached; the traded contracts are declared; an Input is given its processor; a Pattern is pointed at that Input; a Rule is pointed at that Pattern; a Robot is given its rules; the robot is started; data is pushed tick by tick; the summary is read; the account is destroyed. The three surfaces differ in spelling alone — the same twelve steps in the same order on each.

Read the diagram in that order and the chain explains itself: the outside world stops at the adapter, your own thinking lives in the input and nowhere else, the pattern is where thinking becomes a decision, the rule is where a decision becomes a fully specified instruction, the robot is the thing you switch on, and the account is what owns the money and remembers what happened. Nothing above the input knows what your processor computed, and nothing below the rule knows why it was asked to trade — which is what lets you replace either end without disturbing the other.

This chapter has deliberately shown no code: it is the map, not the manual. The chapters that follow give each stage its complete surface on all three languages, and the examples chapter opens with this same assembly written out end to end, from a moving-average crossover to a book-imbalance market maker.

Version 5.0.0.0