Updated for current architecture (shared runtime + strategy manifests/adapters).
This document is the single source of truth for strategy implementation contracts:
- strategy
core.tsresponsibilities - shared runtime responsibilities
StrategyDecisionshape- AI/ML adapters and manifest policy
strategyApimethod reference
Strategy/plugin code should import runtime helpers from explicit public @tradejs/core/* and @tradejs/node/* subpaths and shared types from @tradejs/types.
- use:
import { createStrategyRuntime } from '@tradejs/node/strategies' - use:
import { CreateStrategyCore } from '@tradejs/types' - do not use internal aliases (
@utils,@constants) - do not use non-public deep imports
- do not rely on test-only helpers under
packages/core/src/utils/testHelpers/*
Recommended structure for src/<Strategy> in a standalone strategy repository
or src/strategies/<Strategy> in a user project:
config.tscore.tsfigures.ts(recommended)strategy.tsmanifest.tsadapters/ai.ts(optional)adapters/ml.ts(optional)hooks.ts(optional)<strategy>.pine(optional for Pine-backed strategies)
core.ts should:
- evaluate entry/exit logic from config + market context
- return a
StrategyDecision(skip,entry, orexit)
core.ts should not:
- call AI prompt pipeline directly
- call ML gRPC directly
- place/close orders directly
Use strategyApi for shared operations.
Thin wiring layer:
- exports a
StrategyRegistryEntry - binds the strategy
manifest,defaults, andcreateCore - does not import or construct the Node runtime
Strategy-local runtime extension point:
nameentryRuntimeDefaults(optional)hooks.*lifecycle hooks (optional)aiAdapter(optional)mlAdapter(optional)
packages/node/src/strategyRuntime.ts handles:
- config resolution
coreexecution- AI/ML enrichment and gating
- order execution
- hook invocation
return strategyApi.skip('NO_SIGNAL');Use strategyApi.entry(...) and provide:
directionorderPlan- optional
code,figures,indicators,additionalIndicators,runtime,signalId
Rules:
entryContextis the source of truth for runtime execution fields.orderPlancontains execution-only details:qtystopLossPricetakeProfits
- if
codeis omitted, it is auto-generated as<STRATEGY_NAME>_<DIRECTION>_ENTRY. timestamp/currentPrice/takeProfitPrice/riskRatioare auto-resolved by sharedstrategyApi.entry(...).
return {
kind: 'exit',
code: 'CLOSE_POSITION_BY_RULE',
closePlan: { price, timestamp, direction },
};Preferred policy sources:
- strategy manifest defaults (
entryRuntimeDefaults) - adapter mapping (
mapEntryRuntimeFromConfig) - rare per-decision override (
decision.runtime)
Runtime merge order:
- manifest defaults
- adapter-derived runtime policy from strategy config
- decision runtime overrides
aiAdapter may provide:
buildPayloadbuildSystemPromptAddonbuildHumanPromptAddonmapEntryRuntimeFromConfig
mlAdapter may provide:
normalizeSignalnormalizeStrategyConfigmapEntryRuntimeFromConfig
Manifest lifecycle hooks:
onInitafterCoreDecisiononSkipbeforeClosePosition(can return{ allow: false, reason? })afterEnrichMlafterEnrichAibeforeEntryGate(can return{ allow: false, reason? })beforePlaceOrderafterPlaceOrderonRuntimeError
Typical use cases:
- close opposite positions before opening a new one
- custom entry/exit gating by session or risk context
- strategy-level telemetry and diagnostics
For Pine-backed strategies:
- keep Pine source in a dedicated
.pinefile inside strategy folder - load and execute Pine through the explicit server-only
@tradejs/node/pineadapter - keep file-system access outside the pure
CreateStrategyCorecontract - map Pine results into normal strategy inputs before evaluating the core
Pine support is limited to strategy modules. Custom indicator plugins use TypeScript; standalone Pine indicator plugins are not supported.
strategyApi is the DSL object passed by shared runtime into createCore(...).
Goals:
- reduce boilerplate in strategy cores
- provide consistent access to runtime context and helper logic
Returns a skip decision.
return strategyApi.skip('NO_SIGNAL');Builds an entry decision + signal through shared builders.
Returns: Promise<entry decision>.
Common fields:
directionorderPlan:qtystopLossPricetakeProfits
- optional:
code,figures,indicators,additionalIndicators,runtime,signalId
Behavior:
- resolves the current closed candle from decision context to fill:
timestampcurrentPrice
- derives
takeProfitPricefromorderPlan.takeProfits:LONG-> max TP priceSHORT-> min TP price
- computes
riskRatioautomatically from direction/current/tp/sl - uses provided
code; if omitted generates<STRATEGY_NAME>_<DIRECTION>_ENTRY
Builds an exit decision from:
direction- optional
code
The shared runtime always resolves exit price and timestamp from the
current closed candle. Strategy cores must not provide manual execution fields
or return raw { kind: 'exit' } objects.
Returns the current closed candle decision context:
candletimestampcurrentPrice(equal tocandle.close)
This method does not advance indicators or load market history.
Returns the current strategy indicator snapshot and optional baseContext.
The snapshot type comes from the strategy's CreateStrategyCore declaration;
callers must not provide a generic type argument.
Returns the current shared BaseStrategyContextSnapshot when available.
Wrapper for:
connector.getPosition(symbol)
Shared TP/SL/risk helper. Returns:
stopLossPricetakeProfitPriceriskRatioqty(whenmaxLossValueis provided)
Creates a bounded trade-cooldown controller. Pass enabled explicitly when
the cooldown is part of strategy behavior; omitting it retains the legacy
BACKTEST-only default. The cooldown boundary is inclusive.
BACKTEST config cells and PARITY runtimes keep isolated controller state. The signals daemon reuses it across reconstructed CRON wrappers through the lifecycle-scoped strategy state key. A one-shot CRON process or daemon restart without a restored lifecycle checkpoint starts with an empty cooldown.
- StrategyAPI does not expose full market history to strategy cores.
- Entry and exit decision fields always come from the current closed candle.
indicatorsStateis already wired with current bar by runtime.indicatorsState.snapshot()is lazy-init safe via shared wrappers.
return async () => {
const position = await strategyApi.getCurrentPosition();
if (position && position.qty > 0) {
return strategyApi.skip('POSITION_EXISTS');
}
const { indicators, baseContext } = strategyApi.getCurrentIndicatorsContext();
if (!indicators || !baseContext) {
return strategyApi.skip('WAIT_DATA');
}
const { currentPrice } = await strategyApi.getDecisionPriceContext();
const { stopLossPrice, takeProfitPrice } =
strategyApi.getDirectionalTpSlPrices({
price: currentPrice,
direction: 'LONG',
takeProfitDelta: 2,
stopLossDelta: 1,
unit: 'percent',
});
return strategyApi.entry({
direction: 'LONG',
orderPlan: {
qty: 1,
stopLossPrice,
takeProfits: [{ rate: 1, price: takeProfitPrice }],
},
});
};- Keep
core.tsfocused on strategy logic only. - Keep figure format standardized (
lines/points/zones) for cross-strategy UI. - Store strategy-specific diagnostics in
additionalIndicators. - Prefer adapter/manifest policy over core-level AI/ML branching.
- Reuse
strategyApihelpers instead of duplicating runtime-aware logic.
packages/types/src/strategy.tspackages/types/src/strategyAdapters.tspackages/node/src/strategyRuntime.tspackages/node/src/strategy/manifests.tspackages/core/src/utils/strategyHelpers/signalBuilders.ts