(def: IndicatorDef
, params: P): Series; } ``` ## Example indicators/custom-indicator ```ts import { defineIndicator, defineStrategy } from '@nexpips/sdk-trading'; /** * Indicateur custom via `defineIndicator` : l'amplitude (range) de la barre * courante. `update` est appelé une fois par barre clôturée et reçoit un * `IndicatorContext`. L'engine accumule les retours dans une `Series`. */ const BarRange = defineIndicator<{ history?: number }, number>({ name: 'BarRange', warmup: () => 1, update: (ctx) => ctx.series.high.at(0) - ctx.series.low.at(0), }); export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const range = api.use(BarRange, {}); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (range.at(0) > 0) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` Reading params and series inside a custom indicator. ## See also * [`defineIndicator`](/en/v1/indicators/define-indicator/) · [`IndicatorDef`](/en/v1/reference/types/indicator-def/) · [`MarketSeries`](/en/v1/reference/types/market-series/) # IndicatorDef ## `IndicatorDef` An opaque handle to an indicator, produced by `defineIndicator()` or supplied as a built-in such as `RSI`. You don’t read its fields — you pass it to `api.use()` to register the indicator and obtain its output series. The phantom `__params` and `__output` fields exist solely to carry the parameter and output types (`TParams`, `TOutput`) through the type system. ## Signature ```ts export interface IndicatorDef { readonly __params?: TParams; readonly __output?: TOutput; } ``` ## Example indicators/custom-indicator ```ts import { defineIndicator, defineStrategy } from '@nexpips/sdk-trading'; /** * Indicateur custom via `defineIndicator` : l'amplitude (range) de la barre * courante. `update` est appelé une fois par barre clôturée et reçoit un * `IndicatorContext`. L'engine accumule les retours dans une `Series`. */ const BarRange = defineIndicator<{ history?: number }, number>({ name: 'BarRange', warmup: () => 1, update: (ctx) => ctx.series.high.at(0) - ctx.series.low.at(0), }); export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const range = api.use(BarRange, {}); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (range.at(0) > 0) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` An indicator handle returned by defineIndicator. ## See also * [`defineIndicator`](/en/v1/indicators/define-indicator/) · [`IndicatorContext`](/en/v1/reference/types/indicator-context/) # InitContext ## `InitContext` **At a glance** — Passed to a strategy’s `init()` hook, called once before the first bar. It is intentionally read-only: you get `account`, `clock` and `log` for one-time setup, but no `order` or market `series`, since no trading happens during initialization. ## Signature ```ts export interface InitContext { readonly account: AccountReader; readonly clock: ClockReader; readonly log: LogApi; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` init() receives an InitContext for one-time setup. ## See also * [`AccountReader`](/en/v1/reference/types/account-reader/) * [`ClockReader`](/en/v1/reference/types/clock-reader/) * [`LogApi`](/en/v1/reference/types/log-api/) # InputApi ## `InputApi` Exposed as `api.input` inside `setup()`, this declares the tunable inputs of your strategy. Each method — `int`, `float`, `bool`, and `enum` — returns the resolved value to use immediately, while the declaration drives the strategy manifest and the UI sliders shown to users. Numeric inputs accept optional `min`, `max`, and `step` bounds. ## Signature ```ts export interface InputApi { int(name: string, def: number, opts?: { min?: number; max?: number; step?: number }): number; float(name: string, def: number, opts?: { min?: number; max?: number; step?: number }): number; bool(name: string, def: boolean): boolean; enum(name: string, def: T, options: readonly T[]): T; } ``` ## Example cookbook/buy-with-rr-stop ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Recette cookbook : achat market avec stop-loss ATR et take-profit en R:R 1:2. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: (api) => { const riskPercent = api.input.float('riskPercent', 1, { min: 0.1, max: 2, step: 0.1 }); return { onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); }, }; }, }); ``` Declaring inputs that drive the strategy's risk/reward. ## See also * [`SetupApi`](/en/v1/reference/types/setup-api/) # LogApi ## `LogApi` **At a glance** — The structured logger exposed as `ctx.log` on every strategy context. Each level (`info`, `warn`, `error`, `debug`) takes a message and an optional `LogContext` object so you can attach structured fields to a log line. ## Signature ```ts export interface LogApi { info(msg: string, context?: LogContext): void; warn(msg: string, context?: LogContext): void; error(msg: string, context?: LogContext): void; debug(msg: string, context?: LogContext): void; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` Use ctx.log to emit structured logs. ## See also * [`LogContext`](/en/v1/reference/types/log-context/) # LogContext ## `LogContext` **At a glance** — A structured context object attached to a log line. It is an open map of arbitrary keys to unknown values, passed as the optional second argument to any `LogApi` method to enrich a log entry with relevant data. ## Signature ```ts export type LogContext = Record; ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` Attach a LogContext to a log call. ## See also * [`LogApi`](/en/v1/reference/types/log-api/) # MarketSeries ## `MarketSeries` The per-bar OHLCV data exposed to your strategy as `ctx.series`. It gives you one [`Series`](/en/v1/reference/types/series/) per column (`open`, `high`, `low`, `close`, `volume`) that you index into with `at(n)`. ## Signature ```ts export type MarketSeries = MarketSeriesView; ``` ## Example indicators/sma-trend ```ts import { defineStrategy, SMA } from '@nexpips/sdk-trading'; /** * Filtre de tendance : n'acheter que lorsque la clôture est au-dessus de * la moyenne mobile simple SMA(50). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const sma = api.use(SMA, { source: 'close', period: 50 }); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (ctx.series.close.at(0) > sma.at(0)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` Access OHLCV columns through ctx.series. ## See also * [`Series`](/en/v1/reference/types/series/) * [`Bar`](/en/v1/reference/types/bar/) * [`Source`](/en/v1/reference/types/source/) # ModifyParams ## `ModifyParams` The arguments for `ctx.order.modifyPosition`, used to adjust an open trade’s protective levels. Both `stopLoss` and `takeProfit` are optional [`PriceLevel`](/en/v1/reference/types/price-level/) values — supply only the ones you want to change, for instance to trail a stop. ## Signature ```ts export interface ModifyParams { stopLoss?: PriceLevel; takeProfit?: PriceLevel; } ``` ## Example order/limit-and-cancel ```ts import { defineStrategy, type OrderTicket } from '@nexpips/sdk-trading'; /** * Ordres en attente : poser un `limitBuy`, l'annuler (`cancel`) s'il n'est * pas rempli en quelques barres, et resserrer le stop d'une position ouverte * (`modifyPosition`). Le `limitBuy` renvoie un `OrderTicket` opaque à conserver. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M15', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: () => { let pending: OrderTicket | null = null; let barsWaiting = 0; return { onBar(ctx) { if (ctx.position.hasPendingOrder && pending) { barsWaiting += 1; if (barsWaiting > 3) { ctx.order.cancel(pending); pending = null; barsWaiting = 0; } return; } if (ctx.position.isLong) { ctx.order.modifyPosition({ stopLoss: { type: 'pips', value: 10 } }); return; } if (ctx.position.isFlat) { const limitPrice = ctx.bar.close - 0.001; pending = ctx.order.limitBuy(limitPrice, { riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); barsWaiting = 0; } }, }; }, }); ``` Adjust the stop-loss or take-profit of an open position. ## See also * [`PriceLevel`](/en/v1/reference/types/price-level/) * [`EntryParams`](/en/v1/reference/types/entry-params/) # OrderTicket ## `OrderTicket` **At a glance** — An opaque correlation handle returned by `OrderApi` methods such as `marketBuy`, `marketSell`, `limitBuy`/`limitSell`, `stopBuy`/`stopSell`, `closePosition` and `modifyPosition`. It deliberately carries no price or fill information — the real state lives on `ctx.position`, and the outcome arrives via the asynchronous events `onOrderFilled`, `onOrderRejected` and `onPositionClosed`. Pass it to `ctx.order.cancel()` to cancel a pending order. ## Signature ```ts export interface OrderTicket { readonly id: string; } ``` ## Example order/limit-and-cancel ```ts import { defineStrategy, type OrderTicket } from '@nexpips/sdk-trading'; /** * Ordres en attente : poser un `limitBuy`, l'annuler (`cancel`) s'il n'est * pas rempli en quelques barres, et resserrer le stop d'une position ouverte * (`modifyPosition`). Le `limitBuy` renvoie un `OrderTicket` opaque à conserver. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M15', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: () => { let pending: OrderTicket | null = null; let barsWaiting = 0; return { onBar(ctx) { if (ctx.position.hasPendingOrder && pending) { barsWaiting += 1; if (barsWaiting > 3) { ctx.order.cancel(pending); pending = null; barsWaiting = 0; } return; } if (ctx.position.isLong) { ctx.order.modifyPosition({ stopLoss: { type: 'pips', value: 10 } }); return; } if (ctx.position.isFlat) { const limitPrice = ctx.bar.close - 0.001; pending = ctx.order.limitBuy(limitPrice, { riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); barsWaiting = 0; } }, }; }, }); ``` Keep an OrderTicket to cancel the pending order later. ## See also * [`marketBuy`](/en/v1/api/order/market-buy/) * [`EntryParams`](/en/v1/reference/types/entry-params/) # PositionReader ## `PositionReader` The single source of truth for your current position, exposed as `ctx.position`. Read flags like `isFlat`, `isLong`, `isShort`, and `hasPendingOrder` to branch your logic, and fields such as `size`, `entryPrice`, `stopLoss`, `takeProfit`, `unrealizedPnl`, and `openTime` to inspect an open trade. Everything is `readonly` — you change state through `ctx.order`, never by mutating this view. ## Signature ```ts export interface PositionReader { readonly isFlat: boolean; readonly isLong: boolean; readonly isShort: boolean; readonly hasPendingOrder: boolean; readonly size: number; readonly entryPrice: number | null; readonly stopLoss: number | null; readonly takeProfit: number | null; readonly unrealizedPnl: number; readonly openTime: number | null; } ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` Check ctx.position before opening a trade. ## See also * [`marketBuy`](/en/v1/api/order/market-buy/) * [`OrderTicket`](/en/v1/reference/types/order-ticket/) # PriceLevel ## `PriceLevel` A discriminated union describing how to express a stop-loss or take-profit. Pick the form that fits your strategy: an absolute `price`, a distance in `pips`, an [`ATR`](/en/v1/indicators/atr/)-based offset (`period` × `multiple`), or a reward-to-risk ratio (`rr`). The engine resolves it to a concrete price at order time. ## Signature ```ts export type PriceLevel = | { type: 'price'; value: number } | { type: 'pips'; value: number } | { type: 'atr'; period: number; multiple: number } | { type: 'rr'; value: number }; ``` ## Example indicators/atr-stop ```ts import { ATR, defineStrategy } from '@nexpips/sdk-trading'; /** * Dimensionner le stop avec la volatilité : un stop-loss exprimé en * multiples d'ATR (`{ type: 'atr' }`) s'élargit quand le marché est agité * et se resserre quand il est calme. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const atr = api.use(ATR, { period: 14 }); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; // N'entrer que si la volatilité courante est mesurable. if (atr.at(0) <= 0) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); }, }; }, }); ``` Express a stop-loss as an ATR multiple. ## See also * [`EntryParams`](/en/v1/reference/types/entry-params/) * [`ATR`](/en/v1/indicators/atr/) # RejectReason ## `RejectReason` **At a glance** — The closed set of reasons an order can be rejected by the market or broker. It is the `reason` field of a `Rejection` delivered to `onOrderRejected`, letting you branch your handling per cause (no liquidity, requote, market closed, and so on). ## Signature ```ts export type RejectReason = | 'no_liquidity' | 'requote' | 'market_closed' | 'insufficient_margin' | 'broker_error' | 'connection_lost' | 'invalid_volume_too_small' | 'invalid_volume_too_large'; ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` Branch on a RejectReason inside onOrderRejected. ## See also * [`Rejection`](/en/v1/reference/types/rejection/) # Rejection ## `Rejection` **At a glance** — Passed to `onOrderRejected` when the broker or market refuses an order. It describes a *recoverable* asynchronous condition (the `reason` is a `RejectReason`), unlike `StrategyInvariantError`, which signals a synchronous bug in your strategy code. ## Signature ```ts export interface Rejection { ticketId: string; reason: RejectReason; message: string; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` onOrderRejected receives a Rejection. ## See also * [`RejectReason`](/en/v1/reference/types/reject-reason/) * [`StrategyInvariantError`](/en/v1/api/errors/strategy-invariant-error/) # RiskConfig ## `RiskConfig` The risk guardrails declared on your [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/). The engine enforces them on every order: `maxRiskPercentPerTrade` caps per-trade exposure, `maxOpenPositions` limits concurrency, `maxDailyLossPercent` halts trading after a daily drawdown, and the optional `maxConsecutiveRejects` stops the bot after repeated broker rejections. ## Signature ```ts export interface RiskConfig { maxRiskPercentPerTrade: number; maxOpenPositions: number; maxDailyLossPercent: number; maxConsecutiveRejects?: number; } ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` Declare risk limits enforced by the engine. ## See also * [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/) # Series ## `Series` An indexed, time-ordered stream of values. Read it with `at(0)` for the current bar and `at(n)` for `n` bars ago — indexing is bounded to the past, so there is no look-ahead. `highest(period)` and `lowest(period)` scan the trailing window, and `length` reports how many values are available. Indicators return a `Series`. ## Signature ```ts export interface Series { at(back: number): T; readonly length: number; highest(period: number): number; lowest(period: number): number; } ``` ## Example indicators/sma-trend ```ts import { defineStrategy, SMA } from '@nexpips/sdk-trading'; /** * Filtre de tendance : n'acheter que lorsque la clôture est au-dessus de * la moyenne mobile simple SMA(50). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const sma = api.use(SMA, { source: 'close', period: 50 }); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (ctx.series.close.at(0) > sma.at(0)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` Read the latest indicator value with at(0). ## See also * [`MarketSeries`](/en/v1/reference/types/market-series/) * [`crossedOver`](/en/v1/api/helpers/crossed-over/) # SetupApi ## `SetupApi` The `api` argument passed to your strategy’s `setup()` function. Call `use()` to register an indicator and receive its output `Series`, and reach for `input` to declare tunable parameters. Everything here runs once during the setup phase, before the first bar — registering indicators or inputs later is not allowed. ## Signature ```ts export interface SetupApi { use(indicator: IndicatorDef, params: P): Series; input: InputApi; } ``` ## Example indicators/ema-crossover ```ts import { crossedOver, crossedUnder, defineStrategy, EMA } from '@nexpips/sdk-trading'; /** * Croisement de moyennes exponentielles. Golden cross (EMA rapide passe * au-dessus de la lente) → entrée ; death cross → sortie. Montre `EMA`, * `crossedOver` et `crossedUnder`. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const fast = api.use(EMA, { source: 'close', period: 12 }); const slow = api.use(EMA, { source: 'close', period: 26 }); return { onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder && crossedOver(fast, slow)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); return; } if (ctx.position.isLong && crossedUnder(fast, slow)) { ctx.order.closePosition('ema death cross'); } }, }; }, }); ``` Registering two EMAs during setup. ## See also * [`InputApi`](/en/v1/reference/types/input-api/) · [`defineIndicator`](/en/v1/indicators/define-indicator/) # Source ## `Source` The derived price input an indicator computes on. Beyond the raw OHLC fields you can pick blended sources: `hl2` (high+low)/2, `hlc3` (high+low+close)/3, or `ohlc4` (open+high+low+close)/4. Most indicators default to `close`. ## Signature ```ts export type Source = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4'; ``` ## Example indicators/sma-trend ```ts import { defineStrategy, SMA } from '@nexpips/sdk-trading'; /** * Filtre de tendance : n'acheter que lorsque la clôture est au-dessus de * la moyenne mobile simple SMA(50). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const sma = api.use(SMA, { source: 'close', period: 50 }); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (ctx.series.close.at(0) > sma.at(0)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` Choose the price source feeding an indicator. ## See also * [`SMA`](/en/v1/indicators/sma/) * [`Series`](/en/v1/reference/types/series/) # Strategy ## `Strategy` The opaque, branded value returned by `defineStrategy()`. You never construct it directly or read its fields — you simply `export default` it from your bot file so the runtime can pick it up. The `__brand` tag exists only to prevent plain objects from being mistaken for a validated strategy at compile time. ## Signature ```ts export interface Strategy { readonly __brand: 'Strategy'; } ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` Exporting the strategy returned by defineStrategy. ## See also * [`defineStrategy`](/en/v1/api/runtime/define-strategy/) # StrategyDefinition ## `StrategyDefinition` The configuration object you pass to `defineStrategy()`. It pins your strategy to a `symbol` and `timeframe`, declares its `risk` policy, optionally requests a window of `marketHistory` bars to warm up indicators, and supplies a `setup` function that registers indicators and inputs and returns the `StrategyHooks` that drive execution. ## Signature ```ts export interface StrategyDefinition { symbol: string; timeframe: Timeframe; risk: RiskConfig; marketHistory?: number; setup: (api: SetupApi) => StrategyHooks; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` A full strategy definition with every lifecycle hook. ## See also * [`defineStrategy`](/en/v1/api/runtime/define-strategy/) · [`StrategyHooks`](/en/v1/reference/types/strategy-hooks/) · [`RiskConfig`](/en/v1/reference/types/risk-config/) · [`Timeframe`](/en/v1/reference/types/timeframe/) # StrategyHooks ## `StrategyHooks` The object your `setup()` returns. `onBar` is the only required hook — it fires once per closed bar and is where most trading logic lives. The rest are optional lifecycle callbacks: `init` runs once at startup, `onTick` fires on every price update, and `onOrderFilled`, `onOrderRejected`, and `onPositionClosed` react to execution events. ## Signature ```ts export interface StrategyHooks { init?(ctx: InitContext): void; onBar(ctx: BarContext): void; onTick?(ctx: TickContext): void; onOrderFilled?(ctx: EventContext, fill: Fill): void; onOrderRejected?(ctx: EventContext, rejection: Rejection): void; onPositionClosed?(ctx: EventContext, closed: ClosedPosition): void; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` Every hook wired up. ## See also * [`BarContext`](/en/v1/reference/types/bar-context/) · [`EventContext`](/en/v1/reference/types/event-context/) · [`defineStrategy`](/en/v1/api/runtime/define-strategy/) # TickContext ## `TickContext` **At a glance** — Passed to `onTick()` for intrabar updates. It extends `BarContext` with the live `bid` and `ask` prices, so you keep full access to the series, position, account and order API while reacting to price movement before the bar closes. ## Signature ```ts export interface TickContext extends BarContext { readonly bid: number; readonly ask: number; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` onTick() receives a TickContext with live bid/ask. ## See also * [`BarContext`](/en/v1/reference/types/bar-context/) # Timeframe ## `Timeframe` The bar interval a strategy runs on, from one-minute (`M1`) candles up to weekly (`W1`). You declare it once in your [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/), and the engine feeds your hooks one closed bar of that size at a time. ## Signature ```ts export type Timeframe = 'M1' | 'M5' | 'M15' | 'M30' | 'H1' | 'H4' | 'D1' | 'W1'; ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` A strategy declares its timeframe and reacts on each closed bar. ## See also * [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/)
(indicator: IndicatorDef
, params: P): Series; input: InputApi; } ``` ## Example indicators/ema-crossover ```ts import { crossedOver, crossedUnder, defineStrategy, EMA } from '@nexpips/sdk-trading'; /** * Croisement de moyennes exponentielles. Golden cross (EMA rapide passe * au-dessus de la lente) → entrée ; death cross → sortie. Montre `EMA`, * `crossedOver` et `crossedUnder`. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const fast = api.use(EMA, { source: 'close', period: 12 }); const slow = api.use(EMA, { source: 'close', period: 26 }); return { onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder && crossedOver(fast, slow)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); return; } if (ctx.position.isLong && crossedUnder(fast, slow)) { ctx.order.closePosition('ema death cross'); } }, }; }, }); ``` Registering two EMAs during setup. ## See also * [`InputApi`](/en/v1/reference/types/input-api/) · [`defineIndicator`](/en/v1/indicators/define-indicator/) # Source ## `Source` The derived price input an indicator computes on. Beyond the raw OHLC fields you can pick blended sources: `hl2` (high+low)/2, `hlc3` (high+low+close)/3, or `ohlc4` (open+high+low+close)/4. Most indicators default to `close`. ## Signature ```ts export type Source = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4'; ``` ## Example indicators/sma-trend ```ts import { defineStrategy, SMA } from '@nexpips/sdk-trading'; /** * Filtre de tendance : n'acheter que lorsque la clôture est au-dessus de * la moyenne mobile simple SMA(50). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5 }, setup: (api) => { const sma = api.use(SMA, { source: 'close', period: 50 }); return { onBar(ctx) { if (!ctx.position.isFlat || ctx.position.hasPendingOrder) return; if (ctx.series.close.at(0) > sma.at(0)) { ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'atr', period: 14, multiple: 2 }, takeProfit: { type: 'rr', value: 2 }, }); } }, }; }, }); ``` Choose the price source feeding an indicator. ## See also * [`SMA`](/en/v1/indicators/sma/) * [`Series`](/en/v1/reference/types/series/) # Strategy ## `Strategy` The opaque, branded value returned by `defineStrategy()`. You never construct it directly or read its fields — you simply `export default` it from your bot file so the runtime can pick it up. The `__brand` tag exists only to prevent plain objects from being mistaken for a validated strategy at compile time. ## Signature ```ts export interface Strategy { readonly __brand: 'Strategy'; } ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` Exporting the strategy returned by defineStrategy. ## See also * [`defineStrategy`](/en/v1/api/runtime/define-strategy/) # StrategyDefinition ## `StrategyDefinition` The configuration object you pass to `defineStrategy()`. It pins your strategy to a `symbol` and `timeframe`, declares its `risk` policy, optionally requests a window of `marketHistory` bars to warm up indicators, and supplies a `setup` function that registers indicators and inputs and returns the `StrategyHooks` that drive execution. ## Signature ```ts export interface StrategyDefinition { symbol: string; timeframe: Timeframe; risk: RiskConfig; marketHistory?: number; setup: (api: SetupApi) => StrategyHooks; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` A full strategy definition with every lifecycle hook. ## See also * [`defineStrategy`](/en/v1/api/runtime/define-strategy/) · [`StrategyHooks`](/en/v1/reference/types/strategy-hooks/) · [`RiskConfig`](/en/v1/reference/types/risk-config/) · [`Timeframe`](/en/v1/reference/types/timeframe/) # StrategyHooks ## `StrategyHooks` The object your `setup()` returns. `onBar` is the only required hook — it fires once per closed bar and is where most trading logic lives. The rest are optional lifecycle callbacks: `init` runs once at startup, `onTick` fires on every price update, and `onOrderFilled`, `onOrderRejected`, and `onPositionClosed` react to execution events. ## Signature ```ts export interface StrategyHooks { init?(ctx: InitContext): void; onBar(ctx: BarContext): void; onTick?(ctx: TickContext): void; onOrderFilled?(ctx: EventContext, fill: Fill): void; onOrderRejected?(ctx: EventContext, rejection: Rejection): void; onPositionClosed?(ctx: EventContext, closed: ClosedPosition): void; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` Every hook wired up. ## See also * [`BarContext`](/en/v1/reference/types/bar-context/) · [`EventContext`](/en/v1/reference/types/event-context/) · [`defineStrategy`](/en/v1/api/runtime/define-strategy/) # TickContext ## `TickContext` **At a glance** — Passed to `onTick()` for intrabar updates. It extends `BarContext` with the live `bid` and `ask` prices, so you keep full access to the series, position, account and order API while reacting to price movement before the bar closes. ## Signature ```ts export interface TickContext extends BarContext { readonly bid: number; readonly ask: number; } ``` ## Example hooks/lifecycle ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Cycle de vie complet : tous les hooks optionnels et les contextes qu'ils * reçoivent — `InitContext` (init), `BarContext` (onBar), `TickContext` * (onTick), `EventContext` + `Fill` / `Rejection` / `ClosedPosition` (events). */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'M5', risk: { maxRiskPercentPerTrade: 0.5, maxOpenPositions: 1, maxDailyLossPercent: 3, maxConsecutiveRejects: 5, }, setup: () => ({ init(ctx) { ctx.log.info('strategy started', { balance: ctx.account.balance, at: ctx.clock.now }); }, onBar(ctx) { if (ctx.position.isFlat && !ctx.position.hasPendingOrder) { ctx.order.marketBuy({ riskPercent: 0.5, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); } }, onTick(ctx) { const spread = ctx.ask - ctx.bid; if (spread > 0.0005) ctx.log.debug('wide spread', { spread }); }, onOrderFilled(ctx, fill) { ctx.log.info('filled', { side: fill.side, price: fill.price, size: fill.size }); }, onOrderRejected(ctx, rejection) { // rejection.reason est un RejectReason (union fermée). ctx.log.warn('order rejected', { reason: rejection.reason, message: rejection.message }); }, onPositionClosed(ctx, closed) { ctx.log.info('position closed', { pnl: closed.realizedPnl }); }, }), }); ``` onTick() receives a TickContext with live bid/ask. ## See also * [`BarContext`](/en/v1/reference/types/bar-context/) # Timeframe ## `Timeframe` The bar interval a strategy runs on, from one-minute (`M1`) candles up to weekly (`W1`). You declare it once in your [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/), and the engine feeds your hooks one closed bar of that size at a time. ## Signature ```ts export type Timeframe = 'M1' | 'M5' | 'M15' | 'M30' | 'H1' | 'H4' | 'D1' | 'W1'; ``` ## Example order/market-buy-simple ```ts import { defineStrategy } from '@nexpips/sdk-trading'; /** * Acheter au marché si on est flat, avec un stop-loss à 20 pips. * * Pattern : entrée propre — vérifier `hasPendingOrder` ET `isFlat` * pour éviter d'envoyer un ordre alors qu'un précédent est encore * en attente de confirmation broker. */ export default defineStrategy({ symbol: 'EURUSD', timeframe: 'H1', risk: { maxRiskPercentPerTrade: 1, maxOpenPositions: 1, maxDailyLossPercent: 5, }, setup: () => ({ onBar(ctx) { if (!ctx.position.isFlat) return; if (ctx.position.hasPendingOrder) return; ctx.order.marketBuy({ riskPercent: 1, stopLoss: { type: 'pips', value: 20 }, takeProfit: { type: 'rr', value: 2 }, }); }, }), }); ``` A strategy declares its timeframe and reacts on each closed bar. ## See also * [`StrategyDefinition`](/en/v1/reference/types/strategy-definition/)