Strategy interface implemented by ClientStrategy. Defines core strategy execution methods.
tick: (symbol: string, strategyName: string) => Promise<IStrategyTickResult>
Single tick of strategy execution with VWAP monitoring. Checks for signal generation (throttled) and TP/SL conditions.
getPendingSignal: (symbol: string, currentPrice: number) => Promise<IPublicSignalRow>
Retrieves the currently active pending signal for the symbol. If no active signal exists, returns null. Used internally for monitoring TP/SL and time expiration.
getScheduledSignal: (symbol: string, currentPrice: number) => Promise<IPublicSignalRow>
Retrieves the currently active scheduled signal for the symbol. If no scheduled signal exists, returns null. Used internally for monitoring scheduled signal activation.
getBreakeven: (symbol: string, currentPrice: number) => Promise<boolean>
Checks if breakeven threshold has been reached for the current pending signal.
Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough to cover transaction costs (slippage + fees) and allow breakeven to be set. Threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 transactions
For LONG position:
For SHORT position:
Special cases:
getStopped: (symbol: string) => Promise<boolean>
Checks if the strategy has been stopped.
Returns the stopped state indicating whether the strategy should cease processing new ticks or signals.
getTotalPercentClosed: (symbol: string) => Promise<number>
Returns how much of the position is still held, as a percentage of totalInvested.
Uses dollar-basis cost-basis replay (DCA-aware). 100% means nothing was closed yet. Decreases with each partial close.
Returns 100 if no pending signal or no partial closes.
getTotalCostClosed: (symbol: string) => Promise<number>
Returns how many dollars of cost basis are still held (not yet closed by partials).
Full position open: equals totalInvested (entries × $100). Decreases with each partial close, increases with each averageBuy().
Returns totalInvested if no pending signal or no partial closes.
getPositionEffectivePrice: (symbol: string) => Promise<number>
Returns the effective (DCA-averaged) entry price for the current pending signal. Returns null if no pending signal exists.
getPositionInvestedCount: (symbol: string) => Promise<number>
Returns the number of DCA entries for the current pending signal. 1 = original entry only. Returns null if no pending signal exists.
getPositionInvestedCost: (symbol: string) => Promise<number>
Returns the total invested cost basis in dollars (entryCount × $100). Returns null if no pending signal exists.
getPositionPnlPercent: (symbol: string, currentPrice: number) => Promise<number>
Returns the unrealized PNL percentage at currentPrice. Accounts for partial closes, DCA entries, slippage and fees. Returns null if no pending signal exists.
getPositionPnlCost: (symbol: string, currentPrice: number) => Promise<number>
Returns the unrealized PNL in dollars at currentPrice. Calculated as: pnlPercentage / 100 × totalInvestedCost. Returns null if no pending signal exists.
getPositionEntries: (symbol: string, timestamp: number) => Promise<{ price: number; cost: number; timestamp: number; }[]>
Returns the list of DCA entry prices and costs for the current pending signal.
Each entry records the price and cost of a single position entry. The first element is always the original priceOpen (initial entry). Each subsequent element is an entry added by averageBuy().
Returns null if no pending signal exists. Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
getPositionPartials: (symbol: string) => Promise<{ type: "profit" | "loss"; percent: number; currentPrice: number; costBasisAtClose: number; entryCountAtClose: number; timestamp: number; }[]>
Returns the history of partial closes for the current pending signal.
Each record includes the type (profit or loss), percentage closed, price, cost basis at close, and timestamp. Used for tracking how the position was partially closed over time.
Returns null if no pending signal exists or no partial closes were executed.
backtest: (symbol: string, strategyName: string, candles: ICandleData[]) => Promise<IStrategyBacktestResult>
Fast backtest using historical candles. Iterates through candles, calculates VWAP, checks TP/SL on each candle.
For scheduled signals: first monitors activation/cancellation, then if activated continues with TP/SL monitoring.
stopStrategy: (symbol: string, backtest: boolean) => Promise<void>
Stops the strategy from generating new signals.
Sets internal flag to prevent getSignal from being called on subsequent ticks. Does NOT force-close active pending signals - they continue monitoring until natural closure (TP/SL/time_expired).
Use case: Graceful shutdown in live trading mode without abandoning open positions.
cancelScheduled: (symbol: string, backtest: boolean, cancelId?: string) => Promise<void>
Cancels the scheduled signal without stopping the strategy.
Clears the scheduled signal (waiting for priceOpen activation). Does NOT affect active pending signals or strategy operation. Does NOT set stop flag - strategy can continue generating new signals.
Use case: Cancel a scheduled entry that is no longer desired without stopping the entire strategy.
activateScheduled: (symbol: string, backtest: boolean, activateId?: string) => Promise<void>
Activates the scheduled signal without waiting for price to reach priceOpen.
Forces immediate activation of the scheduled signal at the current price. Does NOT affect active pending signals or strategy operation. Does NOT set stop flag - strategy can continue generating new signals.
Use case: User-initiated early activation of a scheduled entry.
closePending: (symbol: string, backtest: boolean, closeId?: string) => Promise<void>
Closes the pending signal without stopping the strategy.
Clears the pending signal (active position). Does NOT affect scheduled signals or strategy operation. Does NOT set stop flag - strategy can continue generating new signals.
Use case: Close an active position that is no longer desired without stopping the entire strategy.
partialProfit: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean, timestamp: number) => Promise<boolean>
Executes partial close at profit level (moving toward TP).
Closes specified percentage of position at current price. Updates _tpClosed, _totalClosed, and _partialHistory state. Persists updated signal state for crash recovery.
Validations:
Use case: User-controlled partial close triggered from onPartialProfit callback.
validatePartialProfit: (symbol: string, percentToClose: number, currentPrice: number) => Promise<boolean>
Checks whether partialProfit would succeed without executing it.
Returns true if all preconditions for a profitable partial close are met:
percentToClose is a finite number in range (0, 100]currentPrice is a positive finite numberNever throws. Safe to call at any time as a pre-flight check.
partialLoss: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean, timestamp: number) => Promise<boolean>
Executes partial close at loss level (moving toward SL).
Closes specified percentage of position at current price. Updates _slClosed, _totalClosed, and _partialHistory state. Persists updated signal state for crash recovery.
Validations:
Use case: User-controlled partial close triggered from onPartialLoss callback.
validatePartialLoss: (symbol: string, percentToClose: number, currentPrice: number) => Promise<boolean>
Checks whether partialLoss would succeed without executing it.
Returns true if all preconditions for a loss-side partial close are met:
percentToClose is a finite number in range (0, 100]currentPrice is a positive finite numberNever throws. Safe to call at any time as a pre-flight check.
trailingStop: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>
Adjusts trailing stop-loss by shifting distance between entry and original SL.
CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL. This prevents error accumulation on repeated calls. Larger percentShift ABSORBS smaller one (updates only towards better protection).
Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
For LONG position (entry=100, originalSL=90, distance=10%):
For SHORT position (entry=100, originalSL=110, distance=10%):
Absorption behavior:
Validations:
Use case: User-controlled trailing stop triggered from onPartialProfit callback.
validateTrailingStop: (symbol: string, percentShift: number, currentPrice: number) => Promise<boolean>
Checks whether trailingStop would succeed without executing it.
Returns true if all preconditions for a trailing SL update are met:
percentShift is a finite number in [-100, 100], non-zerocurrentPrice is a positive finite numberNever throws. Safe to call at any time as a pre-flight check.
trailingTake: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>
Adjusts the trailing take-profit distance for an active pending signal.
CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP. This prevents error accumulation on repeated calls. Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance. Negative percentShift brings TP closer to entry (more conservative). Positive percentShift moves TP further from entry (more aggressive).
Absorption behavior:
Price intrusion protection: If current price has already crossed the new TP level, the update is skipped to prevent immediate TP triggering.
validateTrailingTake: (symbol: string, percentShift: number, currentPrice: number) => Promise<boolean>
Checks whether trailingTake would succeed without executing it.
Returns true if all preconditions for a trailing TP update are met:
percentShift is a finite number in [-100, 100], non-zerocurrentPrice is a positive finite numberNever throws. Safe to call at any time as a pre-flight check.
breakeven: (symbol: string, currentPrice: number, backtest: boolean) => Promise<boolean>
Moves stop-loss to breakeven (entry price) when price reaches threshold.
Moves SL to entry price (zero-risk position) when current price has moved far enough in profit direction to cover transaction costs (slippage + fees). Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
Behavior:
For LONG position (entry=100, slippage=0.1%, fee=0.1%):
For SHORT position (entry=100, slippage=0.1%, fee=0.1%):
Validations:
Use case: User-controlled breakeven protection triggered from onPartialProfit callback.
validateBreakeven: (symbol: string, currentPrice: number) => Promise<boolean>
Checks whether breakeven would succeed without executing it.
Returns true if all preconditions for moving SL to breakeven are met:
currentPrice is a positive finite number(CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2)false on repeat)Never throws. Safe to call at any time as a pre-flight check.
averageBuy: (symbol: string, currentPrice: number, backtest: boolean, timestamp: number, cost?: number) => Promise<boolean>
Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
Appends currentPrice to the _entry array. The effective entry price used in all distance and PNL calculations becomes the simple arithmetic mean of all _entry prices. Original priceOpen is preserved unchanged for identity/audit purposes.
Rejection rules (returns false without throwing):
Validations (throws):
validateAverageBuy: (symbol: string, currentPrice: number) => Promise<boolean>
Checks whether averageBuy would succeed without executing it.
Returns true if all preconditions for a DCA entry are met:
currentPrice is a positive finite numbercurrentPrice is below the all-time lowest entry price
(or CC_ENABLE_DCA_EVERYWHERE is set)currentPrice is above the all-time highest entry price
(or CC_ENABLE_DCA_EVERYWHERE is set)Never throws. Safe to call at any time as a pre-flight check.
hasPendingSignal: (symbol: string) => Promise<boolean>
Checks if there is an active pending signal for the symbol.
Used internally to determine if TP/SL monitoring should occur on tick.
getPositionEstimateMinutes: (symbol: string) => Promise<number>
Returns the original estimated duration for the current pending signal.
Reflects minuteEstimatedTime as set in the signal DTO — the maximum
number of minutes the position is expected to be active before time_expired.
Returns null if no pending signal exists.
getPositionCountdownMinutes: (symbol: string, timestamp: number) => Promise<number>
Returns the remaining time before the position expires, clamped to zero.
Computes elapsed minutes since pendingAt and subtracts from minuteEstimatedTime.
Returns 0 once the estimate is exceeded (never negative).
Returns null if no pending signal exists.
getPositionHighestProfitPrice: (symbol: string) => Promise<number>
Returns the best price reached in the profit direction during this position's life.
Returns null if no pending signal exists.
getPositionHighestPnlPercentage: (symbol: string) => Promise<number>
Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
Returns null if no pending signal exists.
getPositionHighestPnlCost: (symbol: string) => Promise<number>
Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
Returns null if no pending signal exists.
getPositionHighestProfitTimestamp: (symbol: string) => Promise<number>
Returns the timestamp when the best profit price was recorded during this position's life.
Returns null if no pending signal exists.
getPositionHighestProfitBreakeven: (symbol: string) => Promise<boolean>
Returns whether breakeven was mathematically reachable at the highest profit price.
Uses the same threshold formula as getBreakeven with the recorded peak price. Returns null if no pending signal exists.
getPositionDrawdownMinutes: (symbol: string, timestamp: number) => Promise<number>
Returns the number of minutes elapsed since the highest profit price was recorded.
Measures how long the position has been pulling back from its peak profit level. Zero when called at the exact moment the peak was set. Grows continuously as price moves away from the peak without setting a new record.
Returns null if no pending signal exists.
dispose: () => Promise<void>
Disposes the strategy instance and cleans up resources.
Called when the strategy is being removed from cache or shut down. Invokes the onDispose callback to notify external systems.