Strategy Runtime Hooks
This section documents the lifecycle hook contract used by the shared strategy runtime.
Hooks can be declared in two places:
- strategy-local hooks in
manifest.tsundermanifest.hooks - project-level shared hooks in
tradejs.config.tsunderhooks
Project-level hooks apply to every strategy loaded by the current project config. Strategy manifest hooks still work and are merged additively. For the same stage, project hooks run before strategy manifest hooks.
Runtime Order
- onInit — once, at strategy creation
- onBar — every candle before
core.ts - afterCoreDecision — after
core.ts, only ifcore.tsactually ran - afterBarDecision — after the final candle decision, whether it came from
onBarorcore.ts - onSkip — only for
skipdecisions - beforeClosePosition — gate, can block close
- afterEnrichMl — only when
decision.signalexists - afterEnrichAi — only when
decision.signalexists - beforeEntryGate — gate, can block entry
- beforePlaceOrder — right before connector order placement
- afterPlaceOrder — after successful order placement
- onRuntimeError — on any runtime or hook error
Canonical Params Shape
Every hook now receives a stage-specific subset of the same nested object:
type HookParams = {
ctx?: StrategyHookCtx;
market?: StrategyHookMarketContext;
decision?: StrategyDecision;
entry?: StrategyHookEntryContext;
ml?: StrategyHookMlContext;
ai?: StrategyHookAiContext;
policy?: StrategyHookPolicyContext;
order?: StrategyHookOrderContext;
error?: StrategyHookErrorPayload;
};
Common Nested Shapes
ctx:
type StrategyHookCtx = {
connector: Connector;
strategyName: string;
userName: string;
symbol: string;
strategyConfig: StrategyConfig;
env: string;
isConfigFromBacktest: boolean;
};
market:
type StrategyHookMarketContext = {
candle?: KlineChartItem;
btcCandle?: KlineChartItem;
data?: KlineChartItem[];
btcData?: KlineChartItem[];
};
entry:
type StrategyHookEntryContext = {
context: StrategyEntrySignalContext;
orderPlan: StrategyEntryOrderPlan;
signal?: Signal;
runtime: {
raw?: StrategyEntryRuntimeOptions;
resolved: StrategyEntryRuntimeOptions;
};
};
ml:
type StrategyHookMlContext = {
config?: StrategyRuntimeMlOptions;
attempted: boolean;
applied: boolean;
result?: Signal['ml'];
skippedReason?:
| 'BACKTEST'
| 'DISABLED'
| 'NO_RUNTIME'
| 'NO_STRATEGY_CONFIG'
| 'NO_THRESHOLD'
| 'NO_RESULT';
};
ai:
type StrategyHookAiContext = {
config?: StrategyRuntimeAiOptions;
attempted: boolean;
applied: boolean;
quality?: number;
skippedReason?: 'BACKTEST' | 'DISABLED' | 'NO_RUNTIME' | 'NO_QUALITY';
};
policy:
type StrategyHookPolicyContext = {
aiQuality?: number;
makeOrdersEnabled: boolean;
minAiQuality: number;
};
order:
type StrategyHookOrderContext = {
result: Signal | string;
};
error:
type StrategyHookErrorPayload = {
stage: StrategyHookStage;
cause: unknown;
};
Gate hooks return this shape when they want to block execution:
type GateOutput = {
allow?: boolean;
reason?: string;
};
Important Notes
- Use
tradejs.config.ts -> hooksfor behavior shared across all strategies in the project, such as shared risk controls, cross-strategy position management, or common order filters. beforeSignalsandafterSignalsare also project-level hooks intradejs.config.ts, but they belong to thesignalscommand lifecycle, not to the per-strategy runtime documented on this page.- Keep strategy-only behavior in
manifest.hookswhen it should apply to one strategy only. entry.runtime.rawis the raw runtime returned bycore.tsthroughstrategyApi.entry(...).entry.runtime.resolvedis the runtime actually used by the shared runtime after merging manifest defaults, adapter config, and the raw decision runtime.afterEnrichMlis about the ML stage, not only ML success. Useml.attempted,ml.applied, andml.skippedReasonto tell whether ML actually ran.afterEnrichAiuses the same pattern for AI with theaiobject.afterCoreDecisionis strict post-core.ts. If the candle was short-circuited inonBar, useafterBarDecisionfor logic that still must observe the final result of that candle.- Non-blocking hooks swallow errors: the runtime logs the failure, calls
onRuntimeError, and continues. - Gate hooks (
beforeClosePosition,beforeEntryGate) also swallow their own errors; if they throw, runtime behaves as if the hook returnedundefined.