This page explains how the framework monitors active positions and scheduled signals in real-time during live trading. It covers VWAP (Volume Weighted Average Price) calculation using recent candles, Take Profit and Stop Loss detection logic, time-based expiration monitoring, and scheduled signal activation checks. For information about the overall live execution loop, see 10.1. For crash recovery and state restoration, see 10.2.
The framework uses VWAP (Volume Weighted Average Price) instead of simple close prices for all monitoring decisions. This reduces the impact of short-term price spikes and provides more accurate entry/exit pricing that reflects actual trading volume.
The VWAP calculation is performed by GET_AVG_PRICE_FN in src/client/ClientStrategy.ts:285-296:
const GET_AVG_PRICE_FN = (candles: ICandleData[]): number => {
const sumPriceVolume = candles.reduce((acc, c) => {
const typicalPrice = (c.high + c.low + c.close) / 3;
return acc + typicalPrice * c.volume;
}, 0);
const totalVolume = candles.reduce((acc, c) => acc + c.volume, 0);
return totalVolume === 0
? candles.reduce((acc, c) => acc + c.close, 0) / candles.length
: sumPriceVolume / totalVolume;
};
Calculation steps:
(high + low + close) / 3The number of recent candles used for VWAP calculation is controlled by GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT src/config/params.ts:11:
| Parameter | Default | Description |
|---|---|---|
CC_AVG_PRICE_CANDLES_COUNT |
5 |
Number of recent candles for VWAP (e.g., last 5 minutes with 1m interval) |
Usage in live mode: ClientExchange.getAveragePrice() fetches the last N candles and computes VWAP. See 4.7 for the public API.
Key decision points:
_pendingSignal exists → monitor TP/SL/time_scheduledSignal exists → monitor timeout/activation/cancellationTake Profit monitoring is performed in CHECK_PENDING_SIGNAL_COMPLETION_FN src/client/ClientStrategy.ts:695-712:
For LONG positions src/client/ClientStrategy.ts:696-703:
if (signal.position === "long" && averagePrice >= signal.priceTakeProfit)
priceTakeProfitFor SHORT positions src/client/ClientStrategy.ts:705-712:
if (signal.position === "short" && averagePrice <= signal.priceTakeProfit)
priceTakeProfitWhen TP is hit, CLOSE_PENDING_SIGNAL_FN is called src/client/ClientStrategy.ts:736-789:
toProfitLossDto(signal, signal.priceTakeProfit) (exact TP price)callbacks.onClose()risk.removeSignal()_pendingSignal stateIStrategyTickResultClosed with closeReason: "take_profit"Using exact TP price ensures:
toProfitLossDto, not in monitoring logicStop Loss monitoring follows the same pattern src/client/ClientStrategy.ts:714-731:
For LONG positions src/client/ClientStrategy.ts:715-722:
if (signal.position === "long" && averagePrice <= signal.priceStopLoss)
priceStopLossFor SHORT positions src/client/ClientStrategy.ts:724-731:
if (signal.position === "short" && averagePrice >= signal.priceStopLoss)
priceStopLossIn CHECK_PENDING_SIGNAL_COMPLETION_FN, checks are performed in this order src/client/ClientStrategy.ts:686-731:
Rationale: Time expiration has highest priority because minuteEstimatedTime is a hard deadline. TP/SL are checked only if time hasn't expired.
Stop Loss is validated at signal generation by VALIDATE_SIGNAL_FN src/client/ClientStrategy.ts:40-185:
For LONG src/client/ClientStrategy.ts:80-84:
if (signal.priceStopLoss >= signal.priceOpen) {
errors.push(`Long: priceStopLoss must be < priceOpen`)
}
For SHORT src/client/ClientStrategy.ts:120-124:
if (signal.priceStopLoss <= signal.priceOpen) {
errors.push(`Short: priceStopLoss must be > priceOpen`)
}
Additionally, CC_MAX_STOPLOSS_DISTANCE_PERCENT limits maximum loss src/client/ClientStrategy.ts:100-110, src/config/params.ts:23.
Time expiration is checked first in CHECK_PENDING_SIGNAL_COMPLETION_FN src/client/ClientStrategy.ts:680-693:
const currentTime = self.params.execution.context.when.getTime();
const signalTime = signal.pendingAt; // CRITICAL: use pendingAt, not scheduledAt!
const maxTimeToWait = signal.minuteEstimatedTime * 60 * 1000;
const elapsedTime = currentTime - signalTime;
if (elapsedTime >= maxTimeToWait) {
return await CLOSE_PENDING_SIGNAL_FN(self, signal, averagePrice, "time_expired");
}
Key timestamp: signal.pendingAt is used, not signal.scheduledAt. This ensures:
pendingAt === scheduledAt (both set to current time)pendingAt is set when signal activates, not when created| Timestamp | Set When | Used For |
|---|---|---|
scheduledAt |
Signal first created (immediate or scheduled) | Tracking signal generation time, scheduled timeout |
pendingAt |
Position becomes active (opened) | Time expiration monitoring, PnL duration |
Example scenario:
t=0 → scheduledAt=0, pendingAt=0 (temporary)t=1000 → pendingAt=1000 (updated)currentTime - pendingAt, not currentTime - scheduledAtThis prevents premature timeout for signals that wait a long time for activation. See src/client/ClientStrategy.ts:510-516 for pendingAt update logic.
CC_MAX_SIGNAL_LIFETIME_MINUTES prevents eternal signals src/config/params.ts:25-29:
| Parameter | Default | Purpose |
|---|---|---|
CC_MAX_SIGNAL_LIFETIME_MINUTES |
1440 (1 day) |
Prevents signals from blocking risk limits for weeks/months |
Validated by VALIDATE_SIGNAL_FN src/client/ClientStrategy.ts:161-171:
if (signal.minuteEstimatedTime > GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) {
errors.push(`minuteEstimatedTime too large (${signal.minuteEstimatedTime} minutes).
Eternal signals block risk limits and prevent new trades.`);
}
Scheduled signals wait for price to reach priceOpen before opening a position. Activation is checked by CHECK_SCHEDULED_SIGNAL_PRICE_ACTIVATION_FN src/client/ClientStrategy.ts:388-422:
For LONG positions src/client/ClientStrategy.ts:395-406:
if (scheduled.position === "long") {
// Check StopLoss FIRST (cancellation has priority)
if (currentPrice <= scheduled.priceStopLoss) {
shouldCancel = true;
}
// Activate if price drops to priceOpen AND SL not hit
else if (currentPrice <= scheduled.priceOpen) {
shouldActivate = true;
}
}
For SHORT positions src/client/ClientStrategy.ts:408-419:
if (scheduled.position === "short") {
// Check StopLoss FIRST
if (currentPrice >= scheduled.priceStopLoss) {
shouldCancel = true;
}
// Activate if price rises to priceOpen AND SL not hit
else if (currentPrice >= scheduled.priceOpen) {
shouldActivate = true;
}
}
Critical ordering: StopLoss check has priority over activation. If both conditions are met (price hit SL and priceOpen), signal is cancelled, not activated.
Scheduled signals are also monitored for timeout by CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN src/client/ClientStrategy.ts:332-386:
const currentTime = self.params.execution.context.when.getTime();
const signalTime = scheduled.scheduledAt; // Timeout counts from scheduledAt
const maxTimeToWait = GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES * 60 * 1000;
const elapsedTime = currentTime - signalTime;
if (elapsedTime >= maxTimeToWait) {
// Cancel scheduled signal - never activated
return IStrategyTickResultCancelled;
}
Key difference: Scheduled timeout uses scheduledAt, not pendingAt, because the signal hasn't activated yet.
| Config Parameter | Default | Purpose |
|---|---|---|
CC_SCHEDULE_AWAIT_MINUTES |
120 (2 hours) |
Maximum time to wait for scheduled signal activation |
When activation conditions are met, ACTIVATE_SCHEDULED_SIGNAL_FN is called src/client/ClientStrategy.ts:459-551:
pendingAt timestamp src/client/ClientStrategy.ts:510-516 → set to activation time_isScheduled: falseonOpen callback src/client/ClientStrategy.ts:524-531IStrategyTickResultOpened src/client/ClientStrategy.ts:533-550| Aspect | Live Mode | Backtest Mode |
|---|---|---|
| Price calculation | exchange.getAveragePrice(symbol) |
GET_AVG_PRICE_FN(candles) |
| Candles source | Real-time API call (last N candles) | Pre-fetched historical array |
| Monitoring frequency | Every tick (interval-based) | Every candle in array |
| State persistence | Written to disk after each change | In-memory only |
| Timestamp source | Date.now() via executionContext.when |
Candle timestamp from array |
In live mode, ClientExchange.getAveragePrice() src/lib/services/connection/ExchangeConnectionService.ts (not shown in provided files, but referenced) performs:
exchange.getCandles(symbol, interval, since, CC_AVG_PRICE_CANDLES_COUNT)GET_AVG_PRICE_FN(candles)Implications:
In backtest mode, ClientStrategy.backtest() src/client/ClientStrategy.ts:1191-1329 processes entire candle array:
for (let i = candlesCount - 1; i < candles.length; i++) {
const recentCandles = candles.slice(i - (candlesCount - 1), i + 1);
const averagePrice = GET_AVG_PRICE_FN(recentCandles);
// Check TP/SL using high/low for accuracy
if (signal.position === "long" && currentCandle.high >= signal.priceTakeProfit) {
shouldClose = true;
closeReason = "take_profit";
}
}
Key differences:
candle.high and candle.low for exact TP/SL detection| Check Type | Live Mode | Backtest Mode |
|---|---|---|
| TP/SL detection | VWAP-based (may miss intra-minute spikes) | high/low-based (detects all spikes) |
| Time resolution | Depends on tick interval | 1 candle = 1 minute (or interval) |
| Price volatility | Smoothed by VWAP over 5 candles | Captured by candle high/low |
Example: If TP is briefly hit during a 1-minute candle but VWAP doesn't reach it:
candle.highThis discrepancy is intentional — live mode prioritizes realistic fills (VWAP) over perfect detection.
Key monitoring loops:
| Parameter | File | Default | Purpose |
|---|---|---|---|
CC_AVG_PRICE_CANDLES_COUNT |
src/config/params.ts:11 | 5 |
Number of candles for VWAP calculation |
CC_SCHEDULE_AWAIT_MINUTES |
src/config/params.ts:6 | 120 |
Maximum time to wait for scheduled signal activation |
CC_MAX_SIGNAL_LIFETIME_MINUTES |
src/config/params.ts:29 | 1440 |
Maximum signal lifetime (prevents eternal signals) |
CC_MIN_TAKEPROFIT_DISTANCE_PERCENT |
src/config/params.ts:17 | 0.1% |
Minimum TP distance to cover fees |
CC_MAX_STOPLOSS_DISTANCE_PERCENT |
src/config/params.ts:23 | 20% |
Maximum SL distance to protect capital |
All parameters can be modified via setConfig() 4.1 before starting live trading.
| Function | File Location | Purpose |
|---|---|---|
GET_AVG_PRICE_FN |
src/client/ClientStrategy.ts:285-296 | Calculate VWAP from candles array |
CHECK_PENDING_SIGNAL_COMPLETION_FN |
src/client/ClientStrategy.ts:675-734 | Monitor active signal for TP/SL/time |
CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN |
src/client/ClientStrategy.ts:332-386 | Check scheduled signal timeout |
CHECK_SCHEDULED_SIGNAL_PRICE_ACTIVATION_FN |
src/client/ClientStrategy.ts:388-422 | Check if scheduled signal should activate |
CLOSE_PENDING_SIGNAL_FN |
src/client/ClientStrategy.ts:736-789 | Close active signal with PnL calculation |
ACTIVATE_SCHEDULED_SIGNAL_FN |
src/client/ClientStrategy.ts:459-551 | Convert scheduled signal to active |
CANCEL_SCHEDULED_SIGNAL_BY_STOPLOSS_FN |
src/client/ClientStrategy.ts:424-457 | Cancel scheduled signal due to SL |