ClientExchange is a client class that implements market data fetching, VWAP calculation, and price/quantity formatting logic for exchange operations. It provides context-aware access to historical and real-time candle data, abstracting the underlying data source (API, database, etc.) defined in IExchangeSchema.
For strategy-level market data access, see Exchange Functions. For schema registration, see Exchange Schemas. For the connection service that instantiates ClientExchange, see Connection Services.
ClientExchange serves as the business logic layer for exchange operations, bridging the gap between user-defined data sources (IExchangeSchema.getCandles) and framework requirements (VWAP calculation, timestamp-aware fetching, precision formatting). It implements the IExchange interface and operates without direct dependency injection, receiving all dependencies through its constructor parameters.
Key Responsibilities:
| Responsibility | Methods | Description |
|---|---|---|
| Historical data fetching | getCandles() |
Fetch candles backwards from execution context time |
| Future data fetching | getNextCandles() |
Fetch candles forward from execution context time (backtest only) |
| VWAP calculation | getAveragePrice() |
Calculate volume-weighted average price from recent candles |
| Price formatting | formatPrice() |
Apply exchange-specific price precision rules |
| Quantity formatting | formatQuantity() |
Apply exchange-specific quantity precision rules |
| Lifecycle events | Callbacks | Trigger onCandleData when data is fetched |
ClientExchange is instantiated by ExchangeConnectionService with parameters combining the user-defined schema and runtime dependencies.
interface IExchangeParams extends IExchangeSchema {
logger: ILogger;
execution: TExecutionContextService;
}
Parameter Details:
| Field | Type | Source | Purpose |
|---|---|---|---|
exchangeName |
ExchangeName |
Schema | Unique exchange identifier |
getCandles |
Function | Schema | User-defined candle fetching logic |
formatPrice |
Function | Schema | User-defined price formatting logic |
formatQuantity |
Function | Schema | User-defined quantity formatting logic |
callbacks |
Optional | Schema | Lifecycle event handlers |
logger |
ILogger |
Injected | Debug logging service |
execution |
TExecutionContextService |
Injected | Execution context (symbol, when, backtest flag) |
The execution parameter provides implicit context propagation using di-scoped, allowing ClientExchange to access the current timestamp (when) and backtest flag without explicit parameters.
Diagram: ClientExchange Position in Architecture
Fetches historical candle data backwards from the execution context timestamp. The since parameter is automatically calculated based on execution.when and the requested limit.
Signature:
getCandles(symbol: string, interval: CandleInterval, limit: number): Promise<ICandleData[]>
Behavior:
execution.when from injected contextsince timestamp: when - (interval × limit)IExchangeSchema.getCandles(symbol, interval, since, limit)callbacks.onCandleData if definedICandleData objects sorted chronologicallyTimestamp Calculation Example:
| Execution Time | Interval | Limit | Since Timestamp |
|---|---|---|---|
| 2024-01-15 12:00:00 | 1m | 100 | 2024-01-15 10:20:00 |
| 2024-01-15 12:00:00 | 1h | 24 | 2024-01-14 12:00:00 |
| 2024-01-15 12:00:00 | 1d | 30 | 2023-12-16 12:00:00 |
Fetches candle data forwards from the execution context timestamp. Used exclusively in backtest mode for fast-forward simulation.
Signature:
getNextCandles(symbol: string, interval: CandleInterval, limit: number): Promise<ICandleData[]>
Behavior:
execution.when from injected contextwhen as since timestamp (start from current time)IExchangeSchema.getCandles(symbol, interval, since, limit)ClientStrategy.backtest() methodUsage Context:
Diagram: Market Data Fetching Flow
getAveragePrice() calculates the Volume-Weighted Average Price (VWAP) using the last N 1-minute candles, where N is defined by CC_AVG_PRICE_CANDLES_COUNT (default: 5).
VWAP = Σ(Typical Price × Volume) / Σ(Volume)
Where Typical Price = (High + Low + Close) / 3
Diagram: VWAP Calculation Algorithm
| Context | Purpose | Frequency |
|---|---|---|
| Signal Generation | Current price for signal validation | Every tick() call |
| TP/SL Monitoring | Current price for take profit / stop loss checks | Every tick() call |
| Live Trading | Real-time price updates | Continuous during active positions |
| Backtesting | Historical price at each timestamp | Per timeframe iteration |
The number of candles used for VWAP is configurable via global configuration:
setConfig({
CC_AVG_PRICE_CANDLES_COUNT: 5 // Default: 5 candles (5 minutes)
});
ClientExchange delegates to user-defined formatting functions to ensure values comply with exchange-specific precision rules.
Signature:
formatPrice(symbol: string, price: number): Promise<string>
Behavior:
45123.456789)IExchangeSchema.formatPrice(symbol, price)"45123.45")Common Precision Rules:
| Exchange Type | Example Input | Example Output | Precision |
|---|---|---|---|
| Crypto (USDT pairs) | 45123.456789 | "45123.45" | 2 decimals |
| Crypto (BTC pairs) | 0.0012345678 | "0.00123456" | 8 decimals |
| Forex | 1.23456789 | "1.23456" | 5 decimals |
| Stocks | 123.456789 | "123.46" | 2 decimals |
Signature:
formatQuantity(symbol: string, quantity: number): Promise<string>
Behavior:
0.123456789)IExchangeSchema.formatQuantity(symbol, quantity)"0.123")Common Lot Size Rules:
| Exchange Type | Example Input | Example Output | Lot Size |
|---|---|---|---|
| Crypto (BTC) | 0.123456789 | "0.123456" | 0.000001 BTC |
| Crypto (ETH) | 1.23456789 | "1.2345" | 0.0001 ETH |
| Stocks | 123.456 | "123" | 1 share |
| Forex | 1000.567 | "1000" | 1 unit |
ClientExchange uses ExecutionContextService to provide timestamp-aware and mode-aware operations without requiring explicit context parameters.
Diagram: Context Propagation to ClientExchange
| Field | Type | Source | Usage in ClientExchange |
|---|---|---|---|
symbol |
string |
ExecutionContext | Passed to all exchange methods |
when |
Date |
ExecutionContext | Used to calculate since timestamp for getCandles() |
backtest |
boolean |
ExecutionContext | Determines if getNextCandles() is available |
Backtest Mode (backtest: true):
execution.when iterates through historical timestamps (e.g., 2024-01-01 00:00, 00:01, 00:02...)getCandles() fetches historical data before each timestampgetNextCandles() fetches future candles for fast-forward simulationLive Mode (backtest: false):
execution.when always equals Date.now() (current time)getCandles() fetches recent historical data (last N candles)getNextCandles() never called (live progression uses real-time monitoring)IExchangeCallbacks defines optional lifecycle event handlers triggered during exchange operations.
onCandleData
Triggered after successful candle data fetch via getCandles() or getNextCandles().
Signature:
onCandleData: (
symbol: string,
interval: CandleInterval,
since: Date,
limit: number,
data: ICandleData[]
) => void
Parameters:
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading pair that was fetched |
interval |
CandleInterval |
Candle interval used (1m, 1h, etc.) |
since |
Date |
Start timestamp for fetched range |
limit |
number |
Number of candles requested |
data |
ICandleData[] |
Array of fetched candles |
Use Cases:
addExchange({
exchangeName: "binance",
getCandles: async (symbol, interval, since, limit) => {
// Fetch from Binance API
},
callbacks: {
onCandleData: (symbol, interval, since, limit, data) => {
console.log(`Fetched ${data.length} candles for ${symbol}`);
console.log(`Range: ${since} to ${data[data.length - 1].timestamp}`);
}
}
});
ExchangeConnectionService manages ClientExchange lifecycle and provides memoized instances.
Memoization Pattern:
ClientExchange instance per exchangeNamelogger, execution (scoped)Instantiation Flow:
ExchangeConnectionService.get() called with exchangeName from MethodContextExchangeSchemaServicelogger from DI containerexecution from scoped DI containernew ClientExchange(params)exchangeNameClientStrategy consumes ClientExchange methods for market data access during signal lifecycle.
Integration Points:
| ClientStrategy Method | Calls ClientExchange Method | Purpose |
|---|---|---|
tick() |
getAveragePrice() |
Get current VWAP for signal generation and TP/SL checks |
tick() |
getCandles() (via strategy schema) |
Fetch historical data for indicator calculations |
backtest() |
getNextCandles() |
Fetch future candles for fast-forward simulation |
formatSignal() |
formatPrice() |
Format signal prices for display |
formatSignal() |
formatQuantity() |
Format position sizes for display |
Both BacktestLogicPrivateService and LiveLogicPrivateService establish execution context before delegating to ClientExchange.
Backtest Flow:
BacktestLogicPrivateService.run() iterates through timeframeExecutionContextService.runInContext() with when: timestampClientStrategy.tick() calls ClientExchange.getAveragePrice()ClientExchange uses execution.when to calculate historical VWAPLive Flow:
LiveLogicPrivateService.run() loops indefinitelyExecutionContextService.runInContext() with when: Date.now()ClientStrategy.tick() calls ClientExchange.getAveragePrice()ClientExchange uses execution.when (current time) to calculate real-time VWAPCandle data returned by getCandles() and getNextCandles() follows the OHLCV format.
Interface:
interface ICandleData {
timestamp: number; // Unix milliseconds
open: number; // Opening price
high: number; // Highest price
low: number; // Lowest price
close: number; // Closing price
volume: number; // Trading volume
}
Field Details:
| Field | Type | Unit | Description |
|---|---|---|---|
timestamp |
number |
Milliseconds (Unix epoch) | Candle opening time |
open |
number |
Quote currency | Price at candle start |
high |
number |
Quote currency | Maximum price during period |
low |
number |
Quote currency | Minimum price during period |
close |
number |
Quote currency | Price at candle end |
volume |
number |
Base currency | Total trading volume |
Chronological Ordering:
getCandles() are sorted by timestamp ascending (oldest first)getNextCandles() are sorted by timestamp ascending (earliest future first)ClientExchange supports multiple candle intervals for data fetching.
Supported Intervals:
| Interval | Description | Milliseconds | Common Use Case |
|---|---|---|---|
"1m" |
1 minute | 60,000 | VWAP calculation, high-frequency strategies |
"3m" |
3 minutes | 180,000 | Short-term signals |
"5m" |
5 minutes | 300,000 | Scalping strategies |
"15m" |
15 minutes | 900,000 | Intraday strategies |
"30m" |
30 minutes | 1,800,000 | Swing trading |
"1h" |
1 hour | 3,600,000 | Standard timeframe analysis |
"2h" |
2 hours | 7,200,000 | Medium-term strategies |
"4h" |
4 hours | 14,400,000 | Position trading |
"6h" |
6 hours | 21,600,000 | Long-term analysis |
"8h" |
8 hours | 28,800,000 | Daily monitoring |
Conversion Utility:
The framework includes interval-to-milliseconds conversion logic internally for timestamp calculations. User-defined getCandles() implementations must support these intervals or throw errors for unsupported ones.
Diagram: ClientExchange Inputs and Outputs
Key Design Patterns:
ExecutionContext for timestamp-based operationsRelated Components: