1. What the engine does
The Trading Strategy Engine is a high-performance backtesting and trading engine. It processes large data volumes, handles complex portfolios, produces extensive statistics, and makes ML and AI models straightforward to integrate. It ships as a single binary — one shared library exposing one narrow C ABI, tse_* functions over Tse* types — with two thin wrappers on top: C++ in namespace tse and Python in module tse. All three surfaces drive the same machine, and tse_abi_version() reports the ABI generation they belong to, currently 6.
The library respects your privacy by construction: it runs on your own premises, and your market data, your strategy code, your models and your results never leave them — the engine reaches the network only where you point it, through the connectors you configure, and on its own behalf only for the licence check the environment chapter describes.
Two audiences are assumed throughout, and the engine is a single answer to both: the researcher who wants thousands of candidates measured the same way, and the engineer who has to put one of those candidates into production without rewriting it. That is why this manual describes one pipeline instead of a research tool and a separate trading tool.
A run has the same shape whatever it is for. Market data arrives tick by tick; your own code turns it into the series you care about; the engine watches those series for the conditions you declared, converts a firing condition into an order, puts the order through the risk checks, executes it, and journals the result. What comes out the far end is not a single number but a book of statistics: every retained trade, the exposure that stood behind it, and the per-period scores a model can learn from.
Everything the engine does is done by a robot: a small graph of named nodes that turns a stream of market data into orders, and orders into a book of statistics. How that graph is assembled is the subject of the chapter on how a robot works.
The engine's claim is that 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; so is a market maker that quotes both sides and amends its live orders as client flow arrives. What differs between them is the code inside one callable and the parameters on the rules — never the shape of the assembly. The examples chapter walks exactly that ladder, from the first rung to the last.
The rest of the manual is ordered as a build. The risk management chapter states what the engine refuses to let a robot do. The chapters on the Input, the Pattern, the Rule and the Robot take the nodes of the graph in the order data flows through them, and the Account chapter covers the object that owns all of them and holds the statistics. The chapter on the environment describes what surrounds a run — contracts, executions, currencies, logging and the ex_post scores; the order-book chapter and the timeserie-tools chapter cover the two specialised subsystems. The examples chapter is a course of complete strategies, and the appendix holds the reference tables.
Why an engine at all
A model can now be written in minutes. A moving average, a gradient booster, a transformer scoring the news — the code for the part that decides is no longer the expensive part, and it gets cheaper every month. What did not get cheaper is being right about money. Position accounting through a reversal; the average cost a realised profit is measured against; the exposure snapshot taken at the instant of the fill and not a tick later; the order that has to be refused before it is sent rather than explained after; two feeds whose timestamps must line up; a simulator that must not accidentally see the future — these are invariants, not code. A generated implementation of any of them is plausible on sight and wrong in a way that surfaces only as a number you believed. A backtest that is wrong still prints a profit.
So the boundary moved rather than vanished. The scarce thing is now the verified machine underneath the strategy, and that is what this library is: the parts that have to be right, and have to behave identically in research and in production. What sits above them — the edge, the feature, the model — is yours, and the engine never looks inside it. Your processor is a function pointer and a pointer the engine does not interpret; whether a person or a model wrote what is behind it changes nothing here.
Three properties make that division worth buying rather than rebuilding. The same binary backtests and trades, so a candidate goes live by swapping its two ends and nothing in between is rewritten. Every candidate is measured the same way, which is what begins to matter once candidates are generated by the thousand: the same statistics for a two-line crossover as for a deep model, and the per-period features a selection model ranks them with. And the boundary is one narrow C ABI under thin C++ and Python wrappers, so whatever language your tooling emits attaches without touching the engine.
The inversion is worth stating plainly. Cheap strategy generation raises the value of a library like this one rather than lowering it: when producing candidates costs nothing, the bottleneck becomes trusting them and comparing them — and trust and comparability are the whole subject of what follows.
From an idea to production
The engine exists to shorten the distance between a research idea and a production robot. The same codebase serves backtesting and trading, which is the single design decision most of the rest follows from. A robot developed against historical data goes live by swapping only its two ends — the market-data source it reads and the execution it trades through — while the strategy graph, the risk checks and the statistics stay untouched. Nothing in between is rewritten, re-tuned or re-implemented on the way to production, and nothing in between can therefore behave differently there.
The built-in Simulator is an execution like any other, and the robot above it cannot tell the difference: whether an order is filled from historical prices or handed to a broker is a property of the execution attached to the account, not of the strategy. That is the mechanical reason a backtested robot and a live one are the same robot rather than two implementations of one idea.
- Runs entirely on the client's own premises — strategy logic and order flow never leave the client's infrastructure.
- Separates the decision-making module from the order-routing pipeline: your data processor plugs into an input as an opaque callable, so its logic need never be exposed to the engine. It can be a moving average, a neural network, a gradient-boosted model, or a call to an external service.
- Built on architectural ideas from Developing & Backtesting Systematic Trading Strategies by Brian G. Peterson (rev. 14 June 2017).
The checks that stand between a decision and an order — position limits, exposure limits, stop-loss and take-profit families, and the venue-resting variants of them — are the subject of the risk management chapter.
Three commitments
Backtesting is built around three commitments, and they are what the engine is measured against.
- First, generalized data ingestion. Any information source that is a time series at all is admissible: market data read from a file, a database or a connector; the output of an ML model; or derived data such as order-flow imbalance computed from the order book.
- Second, standardized statistics for every strategy candidate. Every run produces SQL-format logging of the retained trades and of the contract and portfolio exposure captured at the moment of each simulated trade — ready for downstream SQL joins with whatever your own processor logged alongside it.
- Third, feature engineering for ML-based candidate selection. The same run that produces the statistics produces the features a model needs in order to rank one candidate against another.
The three are deliberately stated as commitments rather than features, because each of them is a promise about the general case. Ingestion does not privilege one vendor's format or one kind of tick; the statistics are the same for a two-line moving average and for a deep model, so candidates remain comparable; and the features are produced by the run itself, not reconstructed afterwards from logs that were written for human eyes.
The data traversal is multithreaded. A single pass over five years of TSLA top-of-book data — three billion BidAsk records — completes in roughly 1,200 seconds on a 24-core / 32-thread i9.
Paper trading collects the same statistics and builds the same features as backtesting, and the engine runs thousands of robots concurrently — identical or different.
The cross-language workflow
The intended workflow crosses languages, because research and production rarely want the same one. Develop and backtest a strategy in Python, where the model tooling lives; use ML to select the best candidate out of the many the engine has scored; persist that candidate with one call, tse_save; load it in C++ with one call, tse_load; attach fresh market adapters and an execution; and put the candidate to trading.
What tse_save writes is a recipe, at persistence schema version 3, not live state: the strategy graph — contracts, inputs, patterns, rules and the robot — and nothing else. A data processor is a function pointer and cannot be serialized, so a savable node references it by a string key that the loading process registers again on its own side.
Market adapters and the execution are deliberately absent from the recipe, which is exactly what makes the recipe portable: the same graph is attached to a CSV replay in research and to a live feed and a broker in production. Each loaded input arrives unbound and is bound to a fresh adapter before the robot starts. Recipes, processor keys and the storage format are covered in the chapter on how a robot works, and the examples chapter walks the whole round trip in code.
The three language surfaces are not tiers of capability. The C ABI is the whole surface, and the wrappers stand directly on it: they add ergonomics — objects with lifetimes and the idioms native to each language — and a set of conventional defaults on top of a C surface that has none, but no behaviour of their own. The appendix tabulates the full name map from every action to its C, C++ and Python spelling, together with those defaults.
The direction of travel is not mandatory. A strategy researched in C++ can be saved and reloaded from Python just as easily, and a team that lives in one language never has to cross the boundary at all — the saved recipe is a convenience, not a required stage of the workflow.
In code, the round trip is a save on the Python side and a load, a bind and a start on the C++ side.
account.save("momentum", "momentum.db")
account.load("momentum", "momentum.db");
account.bindInput("sma", market);
account.start("momentum");
Version 5.0.0.0