Write a strategy in TypeScript with decorators¶
The Node binding takes plain object literals as strategies. That works, but TypeScript users tend to want classes and a typed surface they can lean on. The @flox-foundation/flox/authoring module is a thin layer on top of that: a class decorator for the symbol set, method decorators for the callbacks, and a compile() adapter that turns a decorated instance into the runtime Strategy object the engine already accepts. No new C++ surface, no separate runtime.
This is the same model Pine Script users reach for, except the language is TypeScript and the engine is flox.
Quick start¶
Install the package and pick a TypeScript version that supports stage 3 decorators (TS 5.0 or newer; the binding's own tsconfig ships with TS 6.0.3).
import * as flox from "@flox-foundation/flox";
import {
compile,
onStart,
onTrade,
strategy,
StrategyBase,
} from "@flox-foundation/flox/authoring";
const reg = new flox.SymbolRegistry();
const btc = Number(reg.addSymbol("bybit", "BTCUSDT", 0.01));
@strategy({ symbols: [btc] })
class SmaCross extends StrategyBase {
private readonly fast = new flox.SMA(10);
private readonly slow = new flox.SMA(30);
@onStart
start(): void {
console.log("strategy starting");
}
@onTrade
onEachTrade(ctx: flox.SymbolContext, t: flox.TradeData, emit: flox.EmitMethods): void {
const f = this.fast.update(t.price);
const s = this.slow.update(t.price);
if (f === null || s === null) return;
if (f > s && ctx.position === 0) emit.marketBuy(0.01);
else if (f < s && ctx.position > 0) emit.marketSell(0.01);
}
}
const runner = new flox.Runner(reg, () => {});
runner.addStrategy(compile(new SmaCross()));
runner.start();
// ... feed trades via runner.onTrade(...) ...
runner.stop();
compile() returns a flox.Strategy object. Anywhere a Strategy is accepted (Runner.addStrategy, BacktestRunner.setStrategy, WalkForwardRunner.setStrategyFactory), a compiled instance works the same way.
Decorators¶
| Decorator | Wires up | Signature |
|---|---|---|
@strategy(opts) |
Class | Records symbols on the class |
@onStart |
Method | (): void |
@onStop |
Method | (): void |
@onTrade |
Method | (ctx, trade, emit): void |
@onBookUpdate |
Method | (ctx, emit): void |
@onBar |
Method | (ctx, bar, emit): void |
The method decorator binds the function to the instance at construction time and stores it on a hidden symbol-keyed map. compile() reads that map and copies the bound functions onto the returned Strategy. Method names do not have to match the callback names; only the decorator matters.
If you decorate the same callback twice on a class, the last one wins. Private methods and static methods are rejected.
Why use it¶
SymbolContext, TradeData and EmitMethods come from the binding's own index.d.ts, so the IDE knows what ctx.position is and what emit.marketBuy expects.
Per-strategy state lives on this: indicators, position counters, debug flags. No closures over a builder, no shared mutable scope between symbols.
The runtime contract does not change. compile() produces the same Strategy object you would have written by hand, so feature parity with the plain-object form is automatic.
What it is not¶
- Not a separate engine. The TS class is sugar over the existing object-literal API. There is no extra dispatch and no new C++ surface.
- Not a replacement for
flox.Strategyin Python or the C++ class. The TS authoring layer is Node-only. - Not a hot-reload mechanism. Reloading a strategy means tearing down the runner and starting a new one. Hot-reload is tracked separately.
Compiling¶
The decorators are stage 3, so no experimentalDecorators flag is needed. A minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "Node10",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}
target: ES2022 matters: stage 3 decorators emit cleaner output above ES2022, and the authoring lib itself is compiled at that level.
See also¶
- Strategy classes for the cross-language Strategy model.
- The full type surface lives in
index.d.tsnext to the package entry point.