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 |
|---|---|---|---|
| Identifiers | client_order_id | clientOrderId | Order id minted on the client side. |
| Identifiers | broker_order_id | brokerOrderId | Order id minted at the execution point. |
| Labels | rule_label | ruleLabel | The rule that produced the order. |
| Labels | robot_id | robotLabel | The robot that owns that rule. |
| Labels | contract_id | symbol | The contract traded. |
| Timestamps | ts_mkt_event | tsMktEventNanoseconds | The market event behind the trade. |
| Timestamps | ts_execution | tsExecutionNanoseconds | The execution itself. |
| Price and quantity | price | price | Execution price. |
| Price and quantity | quantity | quantity | Executed quantity. |
| Price and quantity | fee | fee | Fee charged on this trade. |
| Price and quantity | booked_pl | bookedPL | Realized P&L booked by this trade. |
| Price and quantity | txn_side_code | txnSide | Side of the transaction. |
| Price and quantity | pos_side_code | posSide | Side of the position it belongs to. |
| Price and quantity | book_mode_code | bookMode | Booking mode of the trade. |
| Price and quantity | execution_code | execution | Execution state of the order. |
| Contract exposure | prev_contract_exposure_quantity | ceQuantity | Position quantity at that instant. |
| Contract exposure | prev_contract_exposure_unrealized_pl | ceUnrealizedPL | Unrealized P&L of that position. |
| Contract exposure | prev_contract_exposure_acquisition_price | ceAcquisitionPrice | Average acquisition price of the position. |
| Contract exposure | prev_contract_exposure_market_price | ceMarketPrice | Marking price of the position. |
| Contract exposure | prev_contract_exposure_side | ceSide | Side of the position. |
| Contract exposure | pos_high_price | ceHigh | Highest price observed on the position. |
| Contract exposure | pos_low_price | ceLow | Lowest price observed on the position. |
| Portfolio exposure | prev_portfolio_exposure_acquisition_value | peAcquisitionValue | Acquisition value of the whole portfolio. |
| Portfolio exposure | prev_portfolio_exposure_market_value | peMarketValue | Market value of the whole portfolio. |
| Portfolio exposure | prev_portfolio_exposure_unrealized_pl | peUnrealizedPL | Unrealized P&L of the whole portfolio. |
| Portfolio exposure | prev_portfolio_exposure_side | peSide | Side 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 |
|---|---|
tsFirstNanoseconds | Execution timestamp of the first transaction in the fold. |
tsLastNanoseconds | Execution timestamp of the last transaction in the fold. |
totalNetProfit | Sum of the booked P&L over the fold. |
grossProfit | Sum 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. |
grossLoss | Sum of the losing round trips on the same basis; profitFactor is the ratio of the two. |
totalNumberOfTrades | How many trades were folded over. |
avgTradeNetProfit | Total net profit divided by that count. |
profitFactor | Gross profit over gross loss; reads 1 when gross loss is zero. |
maxEquity | Running maximum of the equity curve. |
minEquity | Running minimum of the same curve. |
maxDrawdown | Largest fall from a running peak of that curve. |
returnOnAccount | Total 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 return —
rolling_mean_return,ew_mean_return,rolling_sharpe,rolling_calmar,rolling_omega,gain_to_pain. - Drawdown and tail risk —
current_drawdown,drawdown_duration,rolling_cvar,rolling_mae,ulcer_index,tail_ratio. - Hit rate and profit factor —
hit_rate,win_rate,profit_factor,pf_momentum,win_loss_ratio. - Trend and autocorrelation structure —
return_autocorr_lag1,acf_sum,pacf,hurst_exponent,mann_kendall_trend,theil_sen_slope,directional_consistency. - Ranking and selection —
rolling_rank,rank_momentum,top_k_fraction,cross_sectional_zscore,information_coefficient,thompson_sampling,composite. - Trade-level efficiency —
entry_efficiency,exit_efficiency,kelly,mfe_pct,expected_holding_duration,switch_cost_adjusted. - Regime and drift —
return_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