All reference chapters

5. Pattern

What a pattern is

A pattern is the decision node of a robot: it observes one or more inputs, referenced by their labels, and emits a trading signal whenever its condition holds. Patterns produce nothing tradeable on their own — a signal becomes an order only through a rule that names the pattern (tse_add_rule_market and the other rule builders take a patternLabel, see the Rule chapter). Each signal carries the timestamp of the input update that produced it and, for formula patterns only, a quantity (see "Signal-carried quantity" below).

Every pattern builder shares the same leading parameters and the same conventions:

  • account — the account handle the pattern is created on; every builder returns TseStatus (tse_ok / tse_error).
  • label — the pattern's unique name; rules bind to it.
  • duration — a TseDuration value, explicit and mandatory. The engine does not derive the duration from the observed inputs; you state it, exactly as you do for inputs.
  • inputLabels, inputCount — the observed inputs as an array of C strings plus a count. Each kind demands an exact count and rejects any other.
  • coreId — mandatory. A non-negative value pins the pattern's worker thread to that core; a negative value runs the pattern without a separate thread. The C++ and Python wrappers default this argument to -1.

The six kinds

tse_pattern.h declares six pattern kinds: comparison, crossover, threshold, peak, timestamp, and formula. The first five are closed analytic conditions parameterized by a TseCmp comparator and scalar arguments; the sixth hands the decision to your own code. TseCmp has six values — tse_cmp_ge, tse_cmp_lt, tse_cmp_gt, tse_cmp_le, tse_cmp_eq, tse_cmp_ne — mirrored as tse::Cmp::getse::Cmp::ne in C++ and tse.Cmp.Getse.Cmp.Ne in Python.

Comparison

Observes exactly two inputs and fires when lhs <cmp> rhs holds for the two input values at the same timestamp. The first label in inputLabels is the left-hand side, the second is the right-hand side.

TseStatus tse_add_pattern_comparison(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, TseCmp comparison, int coreId);
void Account::addPatternComparison(std::string label, Duration duration, std::vector<std::string> inputLabels, Cmp comparison, int coreId = -1) &;
Account.add_pattern_comparison(label, duration, input_labels, comparison, core_id=-1)

Crossover

Observes exactly two inputs and fires on the bar where lhs crosses rhs: the comparison was false on the previous pair of values and becomes true on the current pair. It therefore needs two data points per input before it can fire at all.

TseStatus tse_add_pattern_crossover(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, TseCmp comparison, int coreId);
void Account::addPatternCrossover(std::string label, Duration duration, std::vector<std::string> inputLabels, Cmp comparison, int coreId = -1) &;
Account.add_pattern_crossover(label, duration, input_labels, comparison, core_id=-1)

Threshold

Observes exactly one input and fires when value <cmp> threshold holds for the input's current value.

TseStatus tse_add_pattern_threshold(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, TseCmp comparison, double threshold, int coreId);
void Account::addPatternThreshold(std::string label, Duration duration, std::vector<std::string> inputLabels, Cmp comparison, double threshold, int coreId = -1) &;
Account.add_pattern_threshold(label, duration, input_labels, comparison, threshold, core_id=-1)

Peak

Observes exactly one input and fires when the input forms a local extremum: over the last three values, with strictly increasing timestamps, the middle value is either strictly above both neighbours (a crest) or strictly below both (a trough). Both directions fire; there is no separate "valley" kind.

TseStatus tse_add_pattern_peak(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, int coreId);
void Account::addPatternPeak(std::string label, Duration duration, std::vector<std::string> inputLabels, int coreId = -1) &;
Account.add_pattern_peak(label, duration, input_labels, core_id=-1)

Timestamp

Observes exactly one input and fires on the clock rather than on values: once the input's timestamp reaches checkPoint, the pattern fires and re-arms itself one coolDown period ahead (aligned to the cool-down grid), so it fires again every coolDown thereafter. Both arguments are epoch-nanoseconds.

TseStatus tse_add_pattern_timestamp(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, int64_t checkPointNanoseconds, int64_t coolDownNanoseconds, int coreId);
void Account::addPatternTimestamp(std::string label, Duration duration, std::vector<std::string> inputLabels, std::int64_t checkPointNanoseconds, std::int64_t coolDownNanoseconds, int coreId = -1) &;
Account.add_pattern_timestamp(label, duration, input_labels, check_point_ns, cool_down_ns, core_id=-1)

Formula

Delegates the firing decision to a user callback and accepts any non-zero number of inputs. The callback contract is the subject of the next section.

TseStatus tse_add_pattern_formula(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, TseFormulaProcessor processor, void* userData, int coreId);
void Account::addPatternFormula(std::string label, Duration duration, std::vector<std::string> inputLabels, FormulaProcessor processor, int coreId = -1) &;
Account.add_pattern_formula(label, duration, input_labels, processor, core_id=-1)

The formula escape hatch

The formula processor is invoked once per input update and decides whether the pattern fires.

When a formula pattern fires, its signal carries a quantity: the value of the triggering update. This is the only channel through which a pattern reports a per-fire quantity, and it is what makes the formula kind the escape hatch for sizing as well as for logic — an input processor can be written to emit a desired trade size as the input value, and the formula pattern forwards that size in its signal.

The callback's parameters and its return convention:

ParameterTypeMeaning
inputLabelchar const*Names the input whose update triggered the call — this is how a multi-input formula discriminates its fan-in: observe several inputs, keep whatever state you need in userData, and branch on the label.
tsNanosecondsint64_tThe update timestamp in epoch-nanoseconds.
valuedoubleThe input's value at that update.
errorBufferchar*To report an error, write a NUL-terminated message into it; a non-empty buffer suppresses the firing and the engine logs the message. Leave the buffer untouched for no error.
errorBufferLengthsize_tThe capacity of errorBuffer.
userDatavoid*The opaque pointer handed to the builder, where the formula keeps its state.
return valueintReturn non-zero to fire the pattern, zero to stay silent.

Its C typedef, declared in tse_pattern.h:

typedef int (*TseFormulaProcessor)(char const* inputLabel, int64_t tsNanoseconds, double value, char* errorBuffer, size_t errorBufferLength, void* userData);

The C++ wrapper form drops the buffer and the userData pointer — capture state in the closure instead; a thrown exception is treated as "do not fire":

using FormulaProcessor = std::function<bool(std::string const& inputLabel, std::int64_t tsNanoseconds, double value)>;

The Python form is any callable with the same three parameters returning a truthy value to fire; an exception raised inside it is swallowed and treated as "do not fire":

def processor(input_label, ts_nanoseconds, value): ...

Signal-carried quantity

A market rule whose TseRuleParams.quantityMode (C++ RuleParams, Python make_rule_params) is tse_quantity_from_signal ignores the quantity field and takes its per-fire quantity from the signal of the pattern it is bound to. The engine applies a signal quantity only when the signal actually carries one and it is strictly positive. Of the six kinds, only formula patterns put a quantity into their signals — the five analytic kinds emit none — so a quantity-from-signal rule is meaningful only when bound to a formula pattern.

tse_enums.h defines TseQuantityMode with the value tse_quantity_from_signal (numeric 3), alongside tse_quantity_undefined, tse_quantity_all and tse_quantity_fixed; tse_quantity_mode_to_string renders any of them as text.

ModeCC++Python
undefined (rejected by rules)tse_quantity_undefinedQuantityMode::undefinedQuantity.Undefined
whole positiontse_quantity_allQuantityMode::allQuantity.All
fixed sizetse_quantity_fixedQuantityMode::fixedQuantity.Fixed
from the pattern signaltse_quantity_from_signalQuantityMode::from_signalQuantity.FromSignal

Persistence

A robot is saved as a recipe (schema version 3, see the environment chapter), and the five analytic kinds are fully described by their builder parameters, so they round-trip through tse_save / tse_load with no extra work. A formula processor, however, is a function pointer that cannot be serialized. To keep a robot with formula patterns savable, register the processor under a string key and build the pattern by key; the recipe then stores the key, and a loading process must register the same key before tse_load.

The by-key builder takes no coreId; a pattern built by key runs without a pinned worker thread. The plain tse_add_pattern_formula still runs normally, but a robot containing such a pattern is unsavable: tse_save fails.

TseStatus tse_register_formula_processor(TseAccountHandle account, char const* key, TseFormulaProcessor processor, void* userData);
TseStatus tse_add_pattern_formula_by_key(TseAccountHandle account, char const* label, TseDuration duration, char const* const* inputLabels, size_t inputCount, char const* key);
void Account::registerFormulaProcessor(std::string key, FormulaProcessor processor) &;
void Account::addPatternFormulaByKey(std::string label, Duration duration, std::vector<std::string> inputLabels, std::string key) &;
Account.register_formula_processor(key, processor)
Account.add_pattern_formula_by_key(label, duration, input_labels, key)

Version 5.0.0.0