12. Examples
The examples live in a dedicated repository: github.com/andreysolovyev381/tse. Each example is a directory holding a C++ and a Python version of the same strategy, and both run against the shipped SDK — the headers, the shared library and the tse.py module described in the preceding chapters.
The examples are deliberately terse. They carry no self-checking, no assertions and no defensive code: each one prints the few numbers its run produced and leaves the judgement to you. Shared plumbing — loading a CSV, a rolling mean, the standard entry and exit rule parameters — lives in a helpers file next to them, so that the body of an example contains only what the example is about.
They are ordered as a course. The first group brings up a robot from nothing, the second builds strategies with it, the third adds risk control, the fourth reads the statistics back, and the last collects the facilities that surround a running engine. This chapter explains the logic of each example and links its code.
The shape every example has
Every example in this chapter is 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. What differs between a moving-average crossover and a book-imbalance market maker is the body of one callable and the parameters on the rules — never the shape below.
Written out, on the three surfaces, that assembly is this. Two things are left outside it: the body of smaProcessor, which is your code and not the engine's, and the parameter aggregate handed to the rule.
TseAccountHandle account = tse_account_create("research", tse_storage_regime_mem, tse_ccy_usd, -1);
TseMarketHandle market = tse_market_create(account, "md", tse_md_ohlcv);
TseSimulatorConfig const options = {100, 100, tse_ohlcv_close, tse_ohlcv_open, tse_bidask_bid, tse_bidask_ask};
tse_exec_create_simulator(account, "sim", &options, 3, -1);
tse_add_contract(account, "TSLA", 1, tse_instrument_equity, tse_underlying_undefined, tse_venue_undefined, 100000);
tse_add_input_ohlcv(account, "sma", 20, tse_duration_days, smaProcessor, &state, market, -1, (char const*[]){"TSLA"}, 1);
tse_add_pattern_threshold(account, "hot", tse_duration_days, (char const*[]){"sma"}, 1, tse_cmp_gt, 42.0, -1);
TseRuleParams const entry = {tse_quantity_fixed, 100.0, tse_price_market, 0.0, 0.0, 0.0, tse_side_long, tse_side_neutral, tse_tif_day, 10};
tse_add_rule_market(account, "entry", tse_rule_entry, &entry, "hot", "TSLA");
tse_add_robot(account, "robot", (char const*[]){"entry"}, 1);
tse_start(account, "robot");
tse_market_push_ohlcv_by_name(market, "TSLA", &tick);
TseSummary summary;
tse_get_summary(account, &summary);
tse_account_destroy(account);
tse::Account account {"research", tse::StorageRegime::mem, tse::Currency::usd, -1};
tse::Market const market {account.createMarket("md", tse::MdType::ohlcv)};
tse::SimulatorConfig const options {100, 100, tse::OhlcvField::close, tse::OhlcvField::open, tse::BidAskField::bid, tse::BidAskField::ask};
account.createSimulator("sim", options, 3, -1);
account.addContract("TSLA", 1, tse::Instrument::equity, tse::Underlying::undefined, tse::Venue::undefined, 100000);
account.addInputOhlcv("sma", 20, tse::Duration::days, smaProcessor, market, {"TSLA"});
account.addPatternThreshold("hot", tse::Duration::days, {"sma"}, tse::Cmp::gt, 42.0);
account.addRuleMarket("entry", tse::RuleType::entry, entryParams, "hot", "TSLA");
account.addRobot("robot", {"entry"});
account.start("robot");
market.pushOhlcv("TSLA", tick);
tse::Summary const summary {account.getSummary()};
account = tse.Account("research", tse.StorageRegime.Mem, tse.Currency.Usd, -1)
market = account.create_market("md", tse.MdType.Ohlcv)
options = tse.make_simulator_config(100, 100, tse.OhlcvField.Close, tse.OhlcvField.Open, tse.BidAskField.Bid, tse.BidAskField.Ask)
account.create_simulator("sim", options, 3, -1)
account.add_contract("TSLA", 1, tse.Instrument.Equity, tse.Underlying.Undefined, tse.Venue.Undefined, 100000)
account.add_input_ohlcv("sma", 20, tse.Duration.Days, sma_processor, market, ["TSLA"])
account.add_pattern_threshold("hot", tse.Duration.Days, ["sma"], tse.Cmp.Gt, 42.0)
account.add_rule_market("entry", tse.RuleType.Entry, entry_params, "hot", "TSLA")
account.add_robot("robot", ["entry"])
account.start("robot")
market.push_ohlcv_by_name("TSLA", tick)
summary = account.get_summary()
Everything that follows is a variation on it.
Hello world
01 hello world - initial params
Process-wide configuration comes before the first account, which is why this is the first example. set_initial_params names the data folder where database-regime blotters write their SQLite files, the log folder, and the log level; an existing folder is taken as it stands, a missing one is created in full, and a file standing where a folder is expected is refused with a diagnostic that surfaces as a TseError.
The lesson worth carrying forward is capture at construction. An account resolves its data folder when it is built, so a later switch never moves the blotter of an account that is already trading: the example builds one account, switches the data folder, builds a second, and shows each blotter file sitting in the folder that was configured at its own construction time.
tse.set_initial_params(data_folder=data_a, log_folder=logs, log_level=tse.LogLevel.Off)
captured_a = tse.Account("CapturedA", tse.StorageRegime.Db)
tse.set_initial_params(data_folder=data_b, log_folder="", log_level=None)
captured_b = tse.Account("CapturedB", tse.StorageRegime.Db)
Code: 01 hello world - initial params
02 hello world - macd
The first complete robot, and the assembly chain every later example repeats: an account, a market adapter, an execution, a contract, an input holding the data processor, patterns over the input, rules bound to the patterns, a robot over the rules, a start, and then the replay of the history.
Pay attention to the stand-alone data processor — this is where the money is made, and it stays yours. Here it is a plain MACD, the distance between a twelve-day and a twenty-six-day exponential average of the close; it pushes that distance into the input's storage and returns readiness only once the slow average has seen enough days, so no rule fires on a half-formed indicator. In later examples the same slot holds a gradient-boosted model, while everything around it is unchanged.
def macd(storage, contract_id, tick):
storage.push(tick.tsNanoseconds, ema_fast - ema_slow)
return storage.size() >= slow
account.add_input_ohlcv("MACD", slow, tse.Duration.Days, macd, market, ["AAPL"])
account.add_pattern_threshold("ToLong", tse.Duration.Days, ["MACD"], tse.Cmp.Ge, 0.0)
Code: 02 hello world - macd
03 hello world - grid search
A backtest exists to manufacture choice. Rather than betting on one lookback, the example sweeps four of them — 10, 20, 50 and 100 days against a 200-day trend — and ends with a pool of candidates to choose from. run_grid takes a builder callback that assembles a fresh account for every parameter value and feeds it the same history; each GridResult carries the parameter value, a success flag and the candidate's full summary, so the selection criterion stays yours.
The grid points are independent backtests, so the engine runs them side by side on a pool of worker threads. The builder notes the identity of the thread it lands on into a set, and the run prints how many distinct workers the pool actually used — the parallelism is visible rather than asserted.
def builder(account, param_value):
worker_threads.add(threading.get_ident())
build_robot(account, int(param_value))
results = tse.run_grid("AAPLGrid", tse.StorageRegime.Mem, params, builder, tse.Currency.Usd)
Code: 03 hello world - grid search
04 hello world - multiple contracts
Three feeds of three different shapes on three adapters: daily AAPL bars, BTC/USDT quotes and WTI trade prints. Two inputs observe the first two instruments — a simple average of the Apple close, a simple average of the Bitcoin mid — while the rules trade a third contract, WTI, which no input watches at all. The long pattern is a threshold on the Apple input, the short one a peak on the Bitcoin input, and the two rules differ in order type: a market order to enter, a limit order to leave. WTI is subscribed to its own price adapter so the portfolio can mark the position it holds.
The part to study is the replay loop. Three feeds share one clock, so the loop keeps an index per feed and always pushes whichever feed holds the oldest unsent tick, using an infinite sentinel for a feed that has run out. The strategy therefore lives through the history in the order it really happened, which is what makes a multi-feed backtest honest.
while aapl_at < len(aapl) or btc_at < len(btc) or wti_at < len(wti):
aapl_ts = aapl[aapl_at].tsNanoseconds if aapl_at < len(aapl) else far_future
btc_ts = btc[btc_at].tsNanoseconds if btc_at < len(btc) else far_future
wti_ts = wti[wti_at].tsNanoseconds if wti_at < len(wti) else far_future
Code: 04 hello world - multiple contracts
05 hello world - multiple adapters
One account, two market-data adapters: daily bars for the equity and a quote stream for the crypto pair. AAPL is traded off the golden cross of its fifty-day and two-hundred-day averages, both inputs reading the bar adapter.
BTC/USDT carries no input and no rule. It is registered as a contract and subscribed to the quote adapter with portfolio_subscribe, which is what makes a non-traded instrument consume a feed: the run finishes with every trade on AAPL and BTC/USDT carrying a live mark against a zero position. Separate the two roles early — observing a feed and trading an instrument are independent decisions.
Code: 05 hello world - multiple adapters
06 hello world - save load
A strategy researched in Python and then traded from C++, across the language boundary. The Python leg registers its two processors under keys, builds the WTI crossover from inputs declared by those keys, runs the backtest, and saves the recipe. Save is recipe-only: contracts, inputs, patterns, rules and the robot travel; market adapters, execution, market data and results deliberately do not.
The C++ leg re-registers the same processor keys — a recipe remembers the name of a processor, never its code — loads the robot, creates today's adapter, binds each restored input to it, and starts. Binding is not optional: a loaded input is unbound until it is given an adapter, and starting with an unbound input is an error. A model's own weights are persisted separately, next to the recipe.
Code: 06 hello world - save load
07 hello world - trading
A robot proven in backtesting goes live by swapping only its two ends, and the example shows both. The IN end is an ordinary market adapter that a live feed pushes into; the OUT end is a custom execution created with create_custom, whose transmit callback receives every order the engine emits and reports fills back with apply_fill.
The callback is written the way a broker behaves rather than the way a simulator does: every second order comes back as two partial fills, at two different latencies, which is exactly the shape of life your strategy has to survive. The strategy body itself is intentionally trivial — any trade print carrying volume is a buy signal, and the order size is that same volume — because the subject here is the seam, not the edge. Only the replay loop and the broker's transmit remain for you to wire to a websocket, a FIX session or a vendor SDK.
def transmit(execution, order):
execution.apply_fill(order.clientOrderId.decode(), price, order.quantity, fee, ts)
broker_execution = account.create_custom("BROKER_EXECUTION", transmit, 8, -1)
Code: 07 hello world - trading
Robots
08 robots - gbm model
The counterpart of 02 hello world - macd: the surroundings are identical, only the processor changed. A gradient-boosted model is trained before the run and then lives inside a bid/ask processor, which derives mid price, spread and quote imbalance from every tick, asks the booster for a forecast, and pushes that forecast as the input's value. Two thresholds at zero put the robot long while the forecast is positive and flat once it turns.
The engine never inspects the callable it was handed; it sees a number and a readiness flag. Whether that number comes from a moving average, a booster, a neural network or an external cloud service is a decision entirely on your side of the boundary, and it does not change a single line of the robot around it.
Code: 08 robots - gbm model
09 robots - rebalance
The rebalance rule maintains an existing position instead of opening or closing one. Its position side names the side it tends, and a firing chains an adjustment order onto the open position. Two timestamp patterns drive the example: the entry pattern carries a cool-down it can never reach, so it fires once; the rebalance pattern rearms one second after firing, so it fires twice.
The arithmetic is deliberately explicit because this is the point most easily misread. The rebalance is additive: it does not measure the distance to a target size and does not bring the position anywhere. Four units at 100, then two more at 103, then two more at 108 leave a position of eight whose acquisition price is the volume-weighted average of the three fills, and nothing is realized along the way because every add joins the side it is already on. A bring-to-target rebalancer is something you compose — with a from-signal quantity, a formula pattern that reads the live position can publish exactly the shortfall it wants filled, the technique 10 robots - market maker uses.
Code: 09 robots - rebalance
10 robots - market maker
A naive two-sided maker with an inventory band. Client order flow arrives as book messages and two book inputs split it: one keeps the client sells the maker buys against, the other keeps the client buys it sells against, and each pushes the client order's quantity into its storage. The four rules use a from-signal quantity, so every order the maker sends is exactly the size of the client order it absorbs.
The quoting logic lives in four formula patterns whose closures read the live position through get_position_state — the pattern layer is ordinary code, so it can consult exposure directly. A small state machine flips between accumulating and unloading: below the upper band the maker quotes both sides, at the band it stops adding and only trades the side that unwinds, until the inventory is back to zero. The adverse-selection guard is named rather than buried — is_every_second_client_order lets the maker quote against every second client order only, which is the crudest possible refusal to keep accumulating against a flow that may be informed.
def is_every_second_client_order(state):
return state["order_count"] % 2 == 0
account.add_pattern_formula("MmOpenLong", tse.Duration.Nanoseconds, ["ClientSells"], open_long)
account.add_rule_market("MmBuyOpen", tse.RuleType.Entry,
from_signal_params(tse.Side.Long, tse.Side.Neutral), "MmOpenLong", "MMTEST")
Code: 10 robots - market maker
11 robots - market maker amend
The maker of the previous example only ever crossed the spread. This one rests a quote and then manages it, which is what order amendment is for. The client flow is read from a recorded file of executed prints, and one measurement pass over each print produces the three facts the maker reacts to: an outsized trade relative to the running average size, a third trade in a row on one side, and a silence longer than ten seconds.
One quote lives at a time, tracked by its client order id, and each amendment does something different to that identity: a cancel drops the id, a replace mints a fresh one, a modify keeps it. A sweep on the quote's own side pulls the quote, a one-sided run steps it away and shrinks it, and a quiet stretch restores it to full size. A separate exit rule unwinds the inventory once it reaches its cap. Read this one together with 21 risk - external tools, oco and amends, which exercises the same three amendment rules in isolation.
account.add_rule_cancel("PullQuote", SYMBOL, 0.0, 0.0, "SweepHit")
account.add_rule_replace("StepAway", SYMBOL, defensive_size, step_away_price, "SameSideRun")
account.add_rule_modify("BackToFullSize", SYMBOL, full_size, 0.0, "QuietGap")
Code: 11 robots - market maker amend
12 robots - voting group
Four robots on one account, where the fourth trades the behaviour of the other three. Three traders ignore the price entirely and vote by a seeded coin flip on every WTI bar — the price feed is only the heartbeat that makes all three decide at the same moments — so their positions come and go for no reason at all, which is precisely what makes the group's aggregate the only thing worth reading.
The group is assembled through the engine's own fills. The replay loop pulls the newly retained trades, skips the voter's own, and pushes the traders' fills into an executed-trade adapter; the voter's input over that adapter keeps their signed sum, adding a long fill and subtracting a short one, and two thresholds over that net put the voter long while the group is net long and flat when the net returns to zero. Per-robot statistics are read back with get_robot_summary, one summary per label, so the three traders and the voter are measured separately on the same account.
account.add_input_executed("Group net", 1, tse.Duration.Minutes, group_net, executed, [CONTRACT])
account.add_pattern_threshold("Group is long", tse.Duration.Minutes, ["Group net"], tse.Cmp.Gt, 0.0)
account.add_pattern_threshold("Group is flat", tse.Duration.Minutes, ["Group net"], tse.Cmp.Le, 0.0)
Code: 12 robots - voting group
13 robots - book imbalance
The first of the three order-book examples, and the one that uses recorded data. The account owns a real L3 book, and the built-in imbalance input feeds the strategy straight from that book: imbalance answers one question — whose resting size is bigger, the buyers' or the sellers' — reading +1 when only bids rest and -1 when only offers do. Two thresholds at plus and minus a fifth drive the robot both ways, with four rules covering entry and exit on each side.
The feed is a file of quote snapshots rather than a hand-written vector. Each snapshot is replayed as the two orders it implies, one per side, and the pair the previous snapshot left resting is cancelled immediately after, so the book always holds the current quote alone instead of a pile of every quote ever seen; a trade print between snapshots gives the portfolio its mark. Note the storage length passed to the input: it is the length of the input's own series, not a book depth.
book = account.create_book("BOOKA", tse.BookLevelKind.L3)
account.add_input_book_imbalance("Imbalance", 4, tse.Duration.Nanoseconds, book, book_mkt, ["BOOKA"])
account.add_pattern_threshold("ToLong", tse.Duration.Nanoseconds, ["Imbalance"], tse.Cmp.Ge, 0.2)
Code: 13 robots - book imbalance
14 robots - book ofi
The same signal as the previous example, run unchanged on all three book depths a venue may publish. The strategy body is depth-agnostic: the only line that differs between the three variants is the book's construction as L1, L2 or L3.
The feed is a laboratory instrument rather than a recording. Each step adds exactly the size that drags the book onto the next target imbalance, so the sequence of signals is known before the run starts and the three variants can be compared field by field. What L2 adds over L1 is per-level state, and what L3 adds over L2 is per-order state — none of which this signal consults, which is the whole point of the comparison. The order book chapter describes what each depth actually retains.
Code: 14 robots - book ofi
15 robots - book arbitrage
The composition pattern for cross-book signals. The same asset is quoted on two venues: an L2 book tracks the first, an L3 book tracks the second, and each feeds its own imbalance input. Neither imbalance is a signal on its own — the strategy waits for the two to disagree by more than 0.3 and bets that the disagreement collapses, leaving when the spread is back inside a narrow band around zero.
The fusion lives in the pattern layer. Each formula closure remembers the last value seen from each input, refuses to answer until both books have spoken, and then compares their difference against its own threshold. The position, meanwhile, is taken in a third and liquid contract that neither book observes, fed by its own price adapter — the signal and the trade are separate choices, as in 04 hello world - multiple contracts.
Code: 15 robots - book arbitrage
16 robots - multileg butterfly
A SPY call butterfly — long one 440 call, short two 450 calls, long one 460 call — submitted as a single multileg transaction. Each leg is a descriptor carrying its own contract, side, quantity, limit price and transaction kind, and the entry and exit prices are read from a data file rather than invented in the source.
The fill is atomic: no leg fills until every leg's market has reached its price, so a half-assembled structure never appears in the portfolio, and if one leg's market runs away nothing fills at all. The exit reverses every leg, again in one all-or-nothing transaction, with each exit leg flipping its side and naming the position side it closes. Two timestamp patterns supply the two moments. Option contracts here carry a hundred-share multiplier, so a dollar on a leg is a hundred dollars of profit and loss.
Code: 16 robots - multileg butterfly
17 robots - multileg strangle hedge
The same atomic mechanics as 16 robots - multileg butterfly, now mixing instrument types inside one transaction. A long CLF26 future carries the directional view, two long out-of-the-money puts at 45 and 44 insure it against a fall, and a far out-of-the-money 60 call buys the upside back: four legs, one hedged position, each leg registered with its own instrument type and a thousand-barrel multiplier.
This is the argument for multileg as a primitive rather than a convenience. A hedge assembled leg by leg exists, for a while, only half built, and that interval is exactly where the risk lives; an all-or-nothing transaction removes the interval. Read the multileg entry of the Rule chapter alongside this example for the exact fill rule.
Code: 17 robots - multileg strangle hedge
Risk
18 risk - internal calc
Risk the engine computes inside itself, in the two forms it offers, measured against a baseline run of the same strategy. The first form is risk policies — pre-trade gates tested against the position an order would leave you holding. The example caps the market value of the whole book at five thousand and the quantity of one name at a hundred shares; once the share price passes fifty, the hundred shares the robot wants are worth more than the value cap and every entry from that point is refused, so the run visibly parts company with the baseline. A third policy confines trading to a repeating window of local time in a named zone, and the example runs it twice: a window a whole day wide, and a window one nanosecond wide, which is a plain way of saying "do not trade at all".
The second form is risk rules — stop-loss and take-profit, each in a fixed and a trailing variant, added with a threshold ratio and the flat-out exit parameters. The fixed pair measures from the entry price, the trailing pair from the best price seen since entry. These rules live in the account and watch the position tick by tick, which makes them fully effective in backtesting; their venue-resting counterparts are the three examples that follow.
Code: 18 risk - internal calc
19 risk - external tools, bracket fixed
The same protection, relocated. A bracket rule submits one entry leg together with a venue risk specification holding a stop-loss leg and a take-profit leg, both fixed, both resting at the venue with a good-till-cancelled time in force. Here the entry is a 580-lot market order on a WTI three-over-ten crossover, guarded two percent below the fill and three percent above it.
The difference from the internal form is where the protection lives. Nothing is recomputed on every tick: when the entry fills, the engine derives the two price triggers from the fill price and the legs' ratios and rests them, so they guard the position even if your process stops running. Whichever side the market touches first closes the position and cancels its sibling, and the whole life cycle — enter, protect, exit — is one rule over one pattern.
risk = tse.make_venue_risk_spec(
tse.make_venue_risk_leg(True, 0, 0.02),
tse.make_venue_risk_leg(True, 0, 0.03),
tse.Tif.Gtc)
account.add_rule_bracket("Bracket", entry, risk, "PatternToLong")
Code: 19 risk - external tools, bracket fixed
20 risk - external tools, bracket trailing
The construction of 19 risk - external tools, bracket fixed with one leg changed. On AAPL daily bars a fifty-over-two-hundred golden cross is protected by a five percent trailing stop and a fixed ten percent target; in the risk leg it is the kind field that selects the behaviour, zero for fixed and one for trailing.
A fixed stop is priced once, off the entry. A trailing stop is re-priced off the best price the position has seen, so it ratchets in the position's favour and converts an open gain into a protected one. Running the two examples side by side shows the difference in character rather than in code: the fixed bracket takes symmetric bites, the trailing bracket lets a winner run and cuts the giveback.
Code: 20 risk - external tools, bracket trailing
21 risk - external tools, oco and amends
Order management as strategy actions, each scenario on its own clean rig and each fired by a timestamp pattern, so the moment of the action is exact.
Three of the four scenarios are the ways of changing your mind about an order already resting at the venue. A limit entry rests below the market, reserving its quantity; then, at the amendment check point, one rule acts on it — a cancel withdraws it and releases the reservation, a modify shrinks it in place from ten to four, a replace pulls it and posts a fresh one at a different price and size. The fourth scenario arms a stop and a target on an open position as one pair: the entry fills long, the pair rests, the price drops through the stop, and the stop leg closes the position while the target dies with it. A specification with both legs absent is rejected; a one-legged one is legal.
Code: 21 risk - external tools, oco and amends
Statistics
22 stats - transaction and robot
Two levels of statistics off a single WTI crossover run. The transaction level is the blotter: get_trades returns one row per retained fill — symbol, price, quantity, booked profit and loss, side — next to the execution's own count of what it processed. This is the record you reconcile against, and it is read straight back from the export layer rather than out of a database file.
The duration level is ex_post. The very same run is cut into day buckets and each bucket is scored, so a model can study how the robot behaved through time instead of reading one number at the end; the example asks for the parameter count and the bucket count, then persists the scores and loads them back to show the round trip. The score parameters are not a fixed table: the axis is as wide as param_count reports, and each column names itself through param_name, which is why the example reads the count rather than assuming it.
ex_post = account.create_ex_post(tse.Duration.Days, -1, 5)
buckets = ex_post.bucket_count(0)
account.ex_post_save(db_path, tse.Duration.Days, -1, 5)
saved_robots = account.ex_post_load(db_path)
Code: 22 stats - transaction and robot
23 stats - candidate selection
A backtest produces N candidates, and choosing between them is itself a repeated task best handed to a model. This example closes the loop between 03 hello world - grid search and 22 stats - transaction and robot: five candidates differing only in their fast lookback are swept with the grid runner, and each builder opens an ex_post object on its own finished account and extracts the score matrix.
The feature vector is the last bucket's row — what shape the candidate was in when its backtest ended — and that single row is everything the model gets to see about it. A booster is trained on every candidate but the last, which is held back, and then ranks all five; the pick is the highest predicted profit, not the highest realized one. Five candidates and a handful of scores are a demonstration size: in practice a candidate carries hundreds to thousands of features, and the same pipeline trains on a CPU.
Code: 23 stats - candidate selection
Everything else
24 misc - core affinity
Every worker in the export surface takes a core identifier as its trailing argument — the account, each execution, each input and each pattern. A non-negative value pins that worker to the named core, and -1 leaves the choice to the operating system.
The example runs one AAPL trend follower twice, once with everything unpinned and once with the two inputs and the two patterns nailed to two cores, and prints both results side by side. They agree, and that is the claim being made: affinity buys latency stability under load, never a different trade. What it costs is that you now own the topology, which is why the unpinned layout remains the sane default outside a latency budget.
Code: 24 misc - core affinity
25 misc - multicurrency
Today the engine is validated on a single currency, US dollar; the euro value and cross-currency portfolios are a feature in the process of shipping. The example below therefore exercises an interface that ships ahead of its cross-currency arithmetic.
An account denominated in euro. The currency is the third argument of account construction, given as an ISO 4217 numeric code, and everything downstream inherits it: prices, cash, booked profit and loss, the summary, the portfolio mark. The strategy itself is a single clean round trip driven by two timestamp patterns — ten units bought at 100 and sold at 110 — because the subject is the unit, not the edge.
The grid runner carries the same parameter, so a whole sweep can be run on a euro book, and the example does exactly that after the single backtest. The engine never converts between currencies: an account is one currency, and mixing them is a decision it refuses to make on your behalf.
Code: 25 misc - multicurrency
26 misc - storage regimes
One robot, two storage regimes. The same WTI crossover backtest runs with the blotter in memory and with the blotter on disk in the configured data folder, the regime being nothing more than the second argument of account construction, and both runs agree on net profit and trade count.
The regime decides where the statistics live, never what the strategy does: memory is the fast research mode, the database regime leaves an auditable file behind, and a combined regime gives both. Because the database regime writes into the folder set by the process-wide parameters, this example and 01 hello world - initial params are two halves of one subject.
Code: 26 misc - storage regimes
27 misc - manual booking
The portfolio without an adapter and without an execution. A contract is registered directly with the portfolio, and fills the engine never placed — a phone order, another desk, an import from a system of record — are booked by hand.
Booking has two modes and the difference matters. The plain mode runs the average-cost engine: a long five at 100 opens the position, a short two at 110 realizes a positive booked profit and leaves three, a final short three flattens it. The engine computes the profit and loss; the caller only reports what traded. The exposure-parameterized mode instead prices the fill against contract and portfolio exposure snapshots the caller supplies — it answers with the profit and leaves the live position exactly as it was, which is how a what-if is costed or an external record imported without disturbing the book. The Account chapter describes both entry points in full.
Code: 27 misc - manual booking
28 misc - bulk actions
Emergency and end-of-day controls, addressed by robot label, in three scenarios built on one rig: an entry that fills at the market and an exit that rests as a limit far above it, so there is always both a position and a resting order to act on.
The first scenario cancels the resting orders and flattens the position without stopping the robot, which promptly trades again — these two actions clean the desk, they do not close it. The second is the panic button that keeps the risk: the robot is halted and the resting exit withdrawn, so the probe price that would have filled that exit now does nothing and the position stays open. The third is the panic button that leaves no risk behind: the robot is halted and the position closed at the market, so every later signal reaches a flat desk. The composite that does all three — halt, clean the venue, go flat, in that order — is the kill switch.
account.cancel_all("BulkRobot")
account.sale_all("BulkRobot")
account.halt_and_cancel_sale_all("BulkRobot")
Code: 28 misc - bulk actions
29 misc - timeserie tools
The data-handling toolkit that surrounds the engine, exercised as one pipeline: a CSV of daily Apple closes is read with an explicit date format, separator and header setting; the index and one named column come back as a timestamp series and a value series; the series is cut down to calendar year 2010 by nanosecond bounds taken as midnight UTC on the first of January in each of two years, so the year is taken whole and neither neighbour leaks in.
The filtered series is written back out with formatted timestamps and then read again, because what the writer put on disk is a series in its own right. Finally head and tail hand back a begin-and-length window into the series rather than a copy of it. These tools exist so that data preparation uses the same arithmetic as the engine itself, and the Timeserie tools chapter documents the transforms the toolkit adds beyond reading and writing.
Code: 29 misc - timeserie tools
Version 5.0.0.0