API overview

A very small vocabulary is enough for all of it. An Input turns ticks into a number; a Pattern watches Inputs and fires; a Rule turns a firing into an order; a Robot owns a set of Rules; an Account owns the robots. That vocabulary is the whole of it, and it does not grow as the strategy gets harder.

A two-line moving-average crossover is written with those five nodes; so is a machine-learned model that finds the pattern for you; so is an imbalance signal computed from the full depth of the order book. What differs between them is the code inside one callable and the parameters on the rules — never the shape of the assembly.

The samples below are Python. Every action exists on all three surfaces and drives the same machine; the surfaces differ in spelling alone — tse_add_input_ohlcv in C, addInputOhlcv in C++, add_input_ohlcv in Python. The appendix of the reference tabulates the full name map from every action to its C, C++ and Python spelling.

Market data

Market data arrives at a named, typed market adapter owned by the account. Named, because inputs refer to it by that name; typed, because the record it accepts is fixed when it is created and validated on every push.

market = account.create_market("MD", tse.MdType.Ohlcv)
market.push_ohlcv_by_name("AAPL", tick)

Inputs

An input hands every tick to your data processor and caches whatever the processor stores. The processor is opaque: the engine holds a function pointer and a pointer it does not interpret, and it never inspects either. The same robot scaffold serves a technical indicator and a neural network alike; only the processor changes.

The builders are add_input_ohlcv, add_input_bidask, add_input_trade, add_input_executed, add_input_book and add_input_book_imbalance.

account.add_input_ohlcv("MACD", 26, tse.Duration.Days, macd, market, ["AAPL"])

Patterns

A pattern observes one or more inputs by label and emits a signal whenever its condition holds. The six kinds are threshold, peak, timestamp, comparison, crossover and formula. Each demands an exact number of observed inputs, and each takes an explicit duration.

account.add_pattern_threshold("ToLong", tse.Duration.Days, ["MACD"], tse.Cmp.Ge, 0.0)

Rules

A rule turns a firing into a fully specified order. Everything the order will carry is fixed when the rule is built, and none of those values has a library-supplied default.

The eight builders are add_rule_market, add_rule_risk, add_rule_multileg, add_rule_bracket, add_rule_oco, add_rule_cancel, add_rule_replace and add_rule_modify.

account.add_rule_market("Entry", tse.RuleType.Entry, entry_params, "ToLong", "AAPL")

Robots and accounts

A robot is declared over rules that already exist and is the unit you start and stop. The account is the root of every run: it owns the contracts, the adapters, the single execution, the portfolio and the blotter.

account.add_robot("Strat", ["Entry", "Exit"])
account.start("Strat")

Risk

Risk lives in two places. A risk policy is a pre-trade gate: a named limit attached to the account and checked before an order is allowed out, on position value, on position quantity or on the trading window. A risk rule is a position watcher: it subscribes to a contract's exposure and emits a closing order when the unrealized-P&L ratio crosses a threshold, fixed or trailing. Brackets and OCO pairs leave the protection resting at the venue instead.

account.add_risk_policy("MaxValue", tse.RiskPolicy.Value, 1_000_000.0, tse.Cmp.Le)
account.add_rule_risk("Stop", tse.RuleType.StopLoss, params, 0.02, "WTI")

Statistics

Every run produces the same statistics for every candidate. A two-line moving average is measured with the numbers that measure a deep model, which is what keeps candidates comparable once they are generated by the thousand. The same run produces the features a selection model ranks them with — produced by the run itself, not reconstructed afterwards from logs written for human eyes. The journal is SQL-format, so it joins with whatever your own processor logged alongside it.

There are two layers. The blotter records one row per executed trade. The analytics fold over those rows: a twelve-field summary for the account and for every robot, and a duration-bucketed scoring axis carrying 78 named score parameters per bucket.

What is recorded for every trade

Every executed trade comes back through get_trades as a TseTrade. When the account is created under a storage regime that includes the database — tse.StorageRegime.Db, or the combined regime — the same trades are also journaled into the retained_trades table of a SQLite file in the configured data folder, so they can be queried with plain SQL and joined against your own logs. Under the memory regime the trades exist only in the account and are read back through the API. The columns fall into six groups: the order identifiers, the labels, the timestamps, the price and quantity block, the contract-exposure snapshot and the portfolio-exposure snapshot.

The two snapshots are the part to read twice. They are taken at the instant of the fill — what the position and the portfolio were holding at that moment, not a tick later — so the context of a trade is recoverable from the row itself.

Group SQL column TseTrade field What it is
Identifiersclient_order_idclientOrderIdOrder id minted on the client side.
Identifiersbroker_order_idbrokerOrderIdOrder id minted at the execution point.
Labelsrule_labelruleLabelThe rule that produced the order.
Labelsrobot_idrobotLabelThe robot that owns that rule.
Labelscontract_idsymbolThe contract traded.
Timestampsts_mkt_eventtsMktEventNanosecondsThe market event behind the trade.
Timestampsts_executiontsExecutionNanosecondsThe execution itself.
Price and quantitypricepriceExecution price.
Price and quantityquantityquantityExecuted quantity.
Price and quantityfeefeeFee charged on this trade.
Price and quantitybooked_plbookedPLRealized P&L booked by this trade.
Price and quantitytxn_side_codetxnSideSide of the transaction.
Price and quantitypos_side_codeposSideSide of the position it belongs to.
Price and quantitybook_mode_codebookModeBooking mode of the trade.
Price and quantityexecution_codeexecutionExecution state of the order.
Contract exposureprev_contract_exposure_quantityceQuantityPosition quantity at that instant.
Contract exposureprev_contract_exposure_unrealized_plceUnrealizedPLUnrealized P&L of that position.
Contract exposureprev_contract_exposure_acquisition_priceceAcquisitionPriceAverage acquisition price of the position.
Contract exposureprev_contract_exposure_market_priceceMarketPriceMarking price of the position.
Contract exposureprev_contract_exposure_sideceSideSide of the position.
Contract exposurepos_high_priceceHighHighest price observed on the position.
Contract exposurepos_low_priceceLowLowest price observed on the position.
Portfolio exposureprev_portfolio_exposure_acquisition_valuepeAcquisitionValueAcquisition value of the whole portfolio.
Portfolio exposureprev_portfolio_exposure_market_valuepeMarketValueMarket value of the whole portfolio.
Portfolio exposureprev_portfolio_exposure_unrealized_plpeUnrealizedPLUnrealized P&L of the whole portfolio.
Portfolio exposureprev_portfolio_exposure_sidepeSideSide of the portfolio.

Each of the four price fields in the contract snapshot carries its own timestamp column, and ceHigh and ceLow hold the sentinels TSE_ABSENT_HIGH_PRICE and TSE_ABSENT_LOW_PRICE while the position has no observed extreme yet. Four further columns classify the order behind the trade — price_type_code, tif_type_code, quantity_type_code and priority_type_code — and stay in the table; the API carries them on TseRetained instead.

Every *_code column is a foreign key into a small seeded lookup table that names its values, and three of those tables are seeded short of the enumeration they name, so resolve a code with a LEFT JOIN rather than an inner one. The insert is idempotent against a compound uniqueness constraint: a fill that reaches the blotter a second time — a replayed message, a reconnect, a repeated run into the same file — is dropped instead of counted twice. All thirty-nine columns and the lookup-table codes are in the appendix of the reference.

What can be computed: the summary

A summary is a fold over booked trades. The whole-account form folds over everything; the per-robot form folds over one robot's trades, and get_summaries returns one summary per robot in the order the robots were added. Twelve fields come back.

Field Meaning
tsFirstNanosecondsExecution timestamp of the first transaction in the fold.
tsLastNanosecondsExecution timestamp of the last transaction in the fold.
totalNetProfitSum of the booked P&L over the fold.
grossProfitSum of the winning round trips, each measured as booked P&L plus fee. Fees are inside this figure, so it does not partition totalNetProfit exactly.
grossLossSum of the losing round trips on the same basis; profitFactor is the ratio of the two.
totalNumberOfTradesHow many trades were folded over.
avgTradeNetProfitTotal net profit divided by that count.
profitFactorGross profit over gross loss; reads 1 when gross loss is zero.
maxEquityRunning maximum of the equity curve.
minEquityRunning minimum of the same curve.
maxDrawdownLargest fall from a running peak of that curve.
returnOnAccountTotal net profit over starting equity.

The equity curve the two extremes are measured on is seated on the account's starting equity. Until starting equity is set it reads zero, which pins returnOnAccount at zero with it.

What can be computed: the scoring axis

The ex-post layer buckets each robot's trades over a duration step and scores every bucket, and every bucket of every robot carries the whole axis. Ask the library for its width rather than assuming it: param_count() reports the number of columns a row occupies, and param_name(i) names each one. Of those columns, 78 are the named score parameters below. The families are these.

  • Return and risk-adjusted returnrolling_mean_return, ew_mean_return, rolling_sharpe, rolling_calmar, rolling_omega, gain_to_pain.
  • Drawdown and tail riskcurrent_drawdown, drawdown_duration, rolling_cvar, rolling_mae, ulcer_index, tail_ratio.
  • Hit rate and profit factorhit_rate, win_rate, profit_factor, pf_momentum, win_loss_ratio.
  • Trend and autocorrelation structurereturn_autocorr_lag1, acf_sum, pacf, hurst_exponent, mann_kendall_trend, theil_sen_slope, directional_consistency.
  • Ranking and selectionrolling_rank, rank_momentum, top_k_fraction, cross_sectional_zscore, information_coefficient, thompson_sampling, composite.
  • Trade-level efficiencyentry_efficiency, exit_efficiency, kelly, mfe_pct, expected_holding_duration, switch_cost_adjusted.
  • Regime and driftreturn_volatility, market_volatility, regime_flip_rate, psi_drift, step_change, rank_entropy.

The axis also carries the summary fields per bucket — net_profit, gross_profit, max_drawdown, return_on_account and the rest — so a bucket is read the same way the account-level fold is. The complete axis, all 78 names, is tabulated in the environment chapter of the reference.

Extraction is two-phase: call a feature reader with null output buffers to learn the row count, then call it again with buffers of that size. Absent values arrive as NaN, so gaps are preserved rather than interpolated.

summary = account.get_summary()
trades = account.get_trades()

ep = account.create_ex_post(tse.Duration.Days, 1, 30)
ts, values = ep.feature_momentum(0)

Version 5.1