8. Account
The account is the root object of every run: all other handles are created from it, and every result is read back through it. This chapter covers the account's own surface — creation and destruction, the portfolio and its exposure snapshots, risk policies, reading and booking trades, result summaries, position resets, and the account-level equity and mode inputs. Throughout, C functions return TseStatus (tse_ok / tse_error) and report detail via tse_last_error(account); the C++ wrapper throws tse::Error carrying that text; the Python wrapper raises tse.TseError.
Lifecycle
An account is created with a label, a storage regime, a currency and a core id. All four are required at the C surface.
The account is also the owner of everything declared on it. Contracts are declared on an existing account with tse_add_contract and live exactly as long as it does: there is no removal call, and destroying the account destroys its contracts together with the markets, executions and books created from it. Nothing you hold has to outlive the account, and no handle taken from it stays valid past its destruction — child handles are borrowed views, not owners.
The storage regime selects the backend of the account's trade blotter — the store every executed trade is journaled into and every summary and trade query is folded over.
| C constant | C++ | Python | Value | Backend |
|---|---|---|---|---|
tse_storage_regime_db | StorageRegime::db | StorageRegime.Db | 1 | on-disk database store |
tse_storage_regime_mem | StorageRegime::mem | StorageRegime.Mem | 2 | in-memory store |
tse_storage_regime_undefined (0) exists in all three surfaces but is not a usable regime. The currency is one of the library's monomorphized set: tse_ccy_usd (840) or tse_ccy_eur (978), the ISO-4217 numeric codes. 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. coreId governs the account's blotter thread: a non-negative value pins the blotter thread to that core, a negative value runs the blotter without a separate thread.
The account owns its market adapters, its single execution and its books. Those child handles (TseMarketHandle, TseExecHandle, TseBookHandle) are borrowed views into the account and become invalid the moment the owning account is destroyed — destroy the account last. The one exception is the ex-post handle, which the caller owns and destroys explicitly, but which still borrows the account it was created from.
Contracts are declared before anything that uses them, and they stay for the account's whole life. Every input, rule, portfolio registration, booking call and trade query resolves contract symbols against the account's declared set: an unknown symbol is an error, and tse_get_trades refuses to run before the first tse_add_contract. There is no call that removes a declared contract.
TseAccountHandle tse_account_create(char const* accountLabel, TseStorageRegime regime,
TseCurrency currency, int coreId);
void tse_account_destroy(TseAccountHandle account);
explicit tse::Account(std::string accountLabel, StorageRegime regime,
Currency currency = Currency::usd, int coreId = -1);
~Account(); /* move-only; the destructor destroys the underlying handle */
account = tse.Account(account_label, regime, currency=tse.Currency.Usd, core_id=-1, lib_path=None)
account.close() # also called by __del__
Portfolio and exposure
The portfolio tracks one exposure node per contract plus an account-wide aggregate. Which (adapter, contract) pairs are marked to market is an explicit selection: a robot's traded contracts are auto-subscribed when the robot starts, and the subscribe calls exist for extra, non-traded contracts you also want marked. Subscribing registers the contract on the adapter. tse_portfolio_add_contract registers a declared contract's position in the portfolio without any market adapter: the position exists and can be moved by manual booking, but it is not marked to market. Registration is idempotent.
Both levels can be snapshotted at any time after the account has processed data.
tse_get_position_state returns tse_error for an unknown contract. isEmpty is not a flatness flag: it marks the sentinel of an absent exposure node — a placeholder position that has never carried state. Whether a position is flat is read from quantity and side. In the same placeholder case the multiple field carries TSE_ABSENT_MULTIPLE (0, mirrored as tse.ABSENT_MULTIPLE in Python), so a consumer needs one check, not two.
The account-wide aggregate is reported as a TsePortfolioState.
| Field | Type |
|---|---|
| marketValue | double |
| acquisitionValue | double |
| unrealizedPL | double |
| side | TseSide |
| latestUpdateNanoseconds | int64_t |
One contract's position is reported as a TsePositionState.
| Field | Type |
|---|---|
| quantity | double |
| marketValue | double |
| acquisitionValue | double |
| unrealizedPL | double |
| acquisitionPrice | double |
| acquisitionPriceTsNanoseconds | int64_t |
| marketPrice | double |
| marketPriceTsNanoseconds | int64_t |
| side | TseSide |
| multiple | int32_t |
| isEmpty | int32_t |
TseStatus tse_portfolio_subscribe(TseAccountHandle, TseMarketHandle market, char const* contractSymbol);
TseStatus tse_portfolio_unsubscribe(TseAccountHandle, TseMarketHandle market, char const* contractSymbol);
TseStatus tse_portfolio_add_contract(TseAccountHandle, char const* contractSymbol);
TseStatus tse_get_portfolio_state(TseAccountHandle, TsePortfolioState* out);
TseStatus tse_get_position_state(TseAccountHandle, char const* contractSymbol, TsePositionState* out);
PortfolioState getPortfolioState() const &;
PositionState getPositionState(std::string contractSymbol) const &; /* isEmpty is a bool here */
state = account.get_portfolio_state() # TsePortfolioState
pos = account.get_position_state(symbol) # TsePositionState
Risk policies
A risk policy is a named, account-attached constraint checked by the engine before an order that would increase exposure is allowed out. Three kinds exist:
| C constant | C++ | Python | Value | Meaning |
|---|---|---|---|---|
tse_risk_policy_value | RiskPolicy::value | RiskPolicy.Value | 0 | limit on position/portfolio market value |
tse_risk_policy_quantity | RiskPolicy::quantity | RiskPolicy.Quantity | 1 | limit on position quantity (per-contract) |
tse_risk_policy_time_period | RiskPolicy::time_period | RiskPolicy.TimePeriod | 2 | trading window |
A threshold policy holds while <metric> <comparison> threshold. The value policy compares market value; the quantity policy compares position quantity and is per-contract only — attached portfolio-wide it never holds. Scope is chosen by the symbol: passing contractSymbol = NULL (C), an empty string (C++), or None (Python) makes the policy portfolio-wide; a contract symbol makes it per-contract. Note the C parameter order: contractSymbol precedes threshold and comparison.
The time-period policy is a trading window on a repeating cycle: cyclePeriodNs is the cycle length, offsetNs the window start inside the cycle, activeDurationNs the window length — all in nanoseconds. The policy holds while the local wall-clock time falls inside the active window. The cycle and the active duration must be positive, the active duration must not exceed the cycle, and the offset is normalized into the cycle. timeZoneName is an IANA zone name ("America/New_York"); NULL or an empty string selects the host machine's current time zone, and an unknown name fails the call with a diagnostic.
While any attached policy does not hold, order submissions and amendments that would increase exposure are refused: the transaction is rolled back, never reaches the execution, and the refusal is logged with the policy's diagnostic. Transactions that do not increase exposure — reductions and closes — are not blocked, so a robot can always trade out of a position that a policy has capped.
TseStatus tse_add_risk_policy(TseAccountHandle, char const* label, TseRiskPolicyType policyType,
char const* contractSymbol, double threshold, TseCmp comparison);
TseStatus tse_add_risk_policy_time_period(TseAccountHandle, char const* label, char const* contractSymbol,
int64_t cyclePeriodNs, int64_t offsetNs,
int64_t activeDurationNs, char const* timeZoneName);
TseStatus tse_remove_risk_policy(TseAccountHandle, char const* label, char const* contractSymbol);
/* note: contractSymbol moves to the last position and may be omitted */
void addRiskPolicy(std::string label, RiskPolicy policyType, double threshold, Cmp comparison,
std::string contractSymbol = std::string()) &;
void addRiskPolicyTimePeriod(std::string label, std::string contractSymbol,
std::int64_t cyclePeriodNs, std::int64_t offsetNs,
std::int64_t activeDurationNs, std::string timeZoneName = std::string()) &;
void removeRiskPolicy(std::string label) &;
void removeRiskPolicy(std::string label, std::string contractSymbol) &;
account.add_risk_policy(label, tse.RiskPolicy.Value, threshold, tse.Cmp.Le, contract_symbol=None)
account.add_risk_policy_time_period(label, contract_symbol, cycle_period_ns, offset_ns,
active_duration_ns, time_zone_name="")
account.remove_risk_policy(label, contract_symbol=None)
Trades: reading and booking
Every executed trade is journaled into the blotter as a TseTrade record: the identifiers and labels, the transaction itself, and two exposure snapshots taken at booking time — the contract exposure (ce* fields) and the portfolio exposure (pe* fields).
| Field | Type |
|---|---|
| clientOrderId | char[32] |
| brokerOrderId | char[32] |
| symbol | char[32] |
| ruleLabel | char[64] |
| robotLabel | char[64] |
| tsMktEventNanoseconds | int64_t |
| tsExecutionNanoseconds | int64_t |
| price | double |
| quantity | double |
| fee | double |
| bookedPL | double |
| txnSide | TseSide |
| posSide | TseSide |
| bookMode | int32_t |
| execution | int32_t |
| ceQuantity | double |
| ceUnrealizedPL | double |
| ceAcquisitionPrice | double |
| ceAcquisitionPriceTsNanoseconds | int64_t |
| ceMarketPrice | double |
| ceMarketPriceTsNanoseconds | int64_t |
| ceHigh | double |
| ceHighTsNanoseconds | int64_t |
| ceLow | double |
| ceLowTsNanoseconds | int64_t |
| ceSide | TseSide |
| peAcquisitionValue | double |
| peMarketValue | double |
| peUnrealizedPL | double |
| peSide | TseSide |
Two sentinels mark a position that carries no observed extreme yet: TSE_ABSENT_HIGH_PRICE (about -9.22e18) in ceHigh and TSE_ABSENT_LOW_PRICE (about +9.22e18) in ceLow, with the matching timestamps written as zero. They are the neutral elements of the running-extreme accumulation, so they can never collide with a real price, which carries at most about 9.22e12. The C++ wrapper exposes them as tse::absentHighPrice / tse::absentLowPrice, Python as tse.ABSENT_HIGH_PRICE / tse.ABSENT_LOW_PRICE.
Reading trades
The reader's boundary conventions are fixed as part of the ABI. A zero fromNanoseconds means the 1970 epoch and a zero toNanoseconds means the 2100 epoch, so zero bounds mean "no bound". A NULL robotLabel (an empty or omitted label in C++ and Python) selects all robots. The buffer protocol is capacity-in / count-out: pass the capacity of outTrades in *inoutCount, and on return it holds the number actually written; when the capacity is smaller than the number of available trades the surplus is silently not written — query the required count first by passing a null outTrades. The C++ and Python wrappers run that two-call protocol internally and return the full list.
TseStatus tse_get_trades(TseAccountHandle, int64_t fromNanoseconds, int64_t toNanoseconds,
char const* robotLabel, TseTrade* outTrades, size_t* inoutCount);
std::vector<Trade> getTrades(std::int64_t fromNanoseconds = 0, std::int64_t toNanoseconds = 0,
std::string robotLabel = std::string()) const &;
trades = account.get_trades(from_nanoseconds=0, to_nanoseconds=0, robot_label=None)
Booking external trades
Externally executed trades enter the account through the booking functions declared in tse_trades.h. Their input record is TseRetained, defined in tse_retained.h — the same retained-trade record that crosses the boundary in the executed market-data feed. No field of it is defaulted by the library: the caller answers for bookMode, execution and priorityType exactly as for price and quantity. Label capacity is 64 bytes (63 useful characters), identifier and symbol capacity is 32 bytes (31 useful characters); longer values are rejected on input with a diagnostic. A contractId of zero means the trade addresses its contract by symbol.
The two modes differ in what they touch. tse_book_trade moves the position: the contract must already be in the portfolio (registered with tse_portfolio_add_contract or subscribed with tse_portfolio_subscribe), and the library snapshots the exposure before the change, applies the trade to the portfolio, then journals it. The trade's posSide field is ignored — the library substitutes the actual side of the position. outBookedPL receives the P&L booked by the journal; on a position reversal it covers the closing leg only.
tse_book_trade_with_exposure is a pure P&L calculator with a journal write: the caller supplies both exposure snapshots (TseContractExposure and TsePortfolioExposure, both declared in tse_trades.h), the portfolio is not touched, and the function is therefore usable for a contract that is not in the portfolio at all. In this mode the trade's posSide is taken exactly as passed and must be definite, as must the side fields of both snapshots. Python folds both modes into the single book_trade method: no snapshots selects the moving mode, both snapshots select the calculator, and exactly one snapshot raises TseError. Python builds the inputs with the tse.make_retained, tse.make_contract_exposure and tse.make_portfolio_exposure helpers.
TseStatus tse_book_trade(TseAccountHandle, TseRetained const* trade, double* outBookedPL);
TseStatus tse_book_trade_with_exposure(TseAccountHandle, TseRetained const* trade,
TseContractExposure const* contractExposure,
TsePortfolioExposure const* portfolioExposure,
double* outBookedPL);
double bookTrade(Retained const& trade) &;
double bookTradeWithExposure(Retained const& trade, ContractExposure const& contractExposure,
PortfolioExposure const& portfolioExposure) &;
pl = account.book_trade(trade) # moves the position
pl = account.book_trade(trade, contract_exposure, portfolio_exposure) # pure P&L calculator
Summaries
A summary is a fold over booked trades. The whole-account form folds over everything; the per-robot form folds over that robot's trades only.
tsFirstNanoseconds and tsLastNanoseconds are the execution timestamps of the first and last transactions in the fold. maxEquity and minEquity are the running extremes of the equity curve seated on the account's starting equity (see the last section); returnOnAccount is the total net profit divided by the starting equity, and reads zero while the starting equity is zero. profitFactor reads 1 when the gross loss is zero. tse_get_summaries fills one TseSummary per robot, in the order the robots were added, with the same capacity-in / count-out protocol as tse_get_trades: pass a null outSummaries to query the robot count without writing.
The fold is reported as a TseSummary record.
| Field | Type |
|---|---|
| tsFirstNanoseconds | int64_t |
| tsLastNanoseconds | int64_t |
| totalNetProfit | double |
| grossProfit | double |
| grossLoss | double |
| totalNumberOfTrades | int64_t |
| avgTradeNetProfit | double |
| profitFactor | double |
| maxEquity | double |
| minEquity | double |
| maxDrawdown | double |
| returnOnAccount | double |
TseStatus tse_get_summary(TseAccountHandle, TseSummary* outSummary);
TseStatus tse_get_robot_summary(TseAccountHandle, char const* robotLabel, TseSummary* outSummary);
TseStatus tse_get_summaries(TseAccountHandle, TseSummary* outSummaries, size_t* inoutCount);
Summary getSummary() const &;
Summary getRobotSummary(std::string robotLabel) const &;
std::vector<Summary> getSummaries() const &;
summary = account.get_summary()
summary = account.get_robot_summary(robot_label)
summaries = account.get_summaries()
Resetting positions
Two calls return exposure to a pristine state without rebuilding the account.
A reset position is equal to a freshly constructed one: the exposure node is replaced by a new node that carries only the contract's multiple, so quantities, values, prices and the observed-extreme history are all zeroed, and the sides return to neutral. The wiring survives — portfolio subscriptions and registered contracts are preserved, so data keeps flowing and the position can be moved again immediately. tse_reset_portfolio resets every contract node and zeroes the portfolio aggregate; tse_reset_position resets one contract and then recomputes the portfolio aggregates from scratch over the remaining positions, and returns tse_error for an unknown contract.
TseStatus tse_reset_portfolio(TseAccountHandle);
TseStatus tse_reset_position(TseAccountHandle, char const* contractSymbol);
void resetPortfolio() &; /* throws tse::Error on failure */
void resetPosition(std::string contractSymbol) &; /* throws tse::Error on failure */
account.reset_portfolio()
account.reset_position(contract_symbol)
Equity and regime
Two account-level inputs feed the summary and scoring projections.
startingEquity drives returnOnAccount and seats the equity-curve baseline that maxEquity and minEquity are measured from; riskFreeRate feeds the Sharpe-family scores of the ex-post projections. Until the call is made both inputs read as zero, which in turn pins returnOnAccount at zero.
tse_account_set_mode declares the account's operating regime: metered backtest robots (tse_mode_backtest, the default) versus a live trading robot (tse_mode_live). One mode per account.
| C constant | C++ | Python | Value | Regime |
|---|---|---|---|---|
tse_mode_backtest | Mode::backtest | Mode.Backtest | 0 | metered backtest robots; the default |
tse_mode_live | Mode::live | Mode.Live | 1 | a live trading robot |
TseStatus tse_set_account_equity(TseAccountHandle, double startingEquity, double riskFreeRate);
TseStatus tse_account_set_mode(TseAccountHandle, TseMode mode);
void setAccountEquity(double startingEquity, double riskFreeRate) &;
void setMode(Mode mode) &;
account.set_account_equity(starting_equity, risk_free_rate)
account.set_mode(tse.Mode.Live)
Version 5.0.0.0