This page documents the public utility functions for interacting with exchange data sources: getCandles, getAveragePrice, formatPrice, formatQuantity, getDate, and getMode. These functions provide a simplified API for accessing market data and exchange-specific formatting without requiring explicit context parameters.
For information about registering exchange schemas, see Component Registration Functions. For details about the underlying exchange client implementation, see ClientExchange. For defining custom exchange integrations, see Exchange Schemas.
Exchange functions leverage the framework's context propagation system to operate without explicit context parameters. When called from within a strategy's getSignal() function or during execution, these functions automatically resolve:
exchangeNamesymbol, when timestamp, backtest flag)This allows strategy authors to write clean code like getCandles(symbol, interval, limit) instead of passing context objects through every function call.
Fetches historical OHLCV candles from the registered exchange, looking backward from the current execution context timestamp.
Signature:
function getCandles(
symbol: string,
interval: CandleInterval,
limit: number
): Promise<ICandleData[]>
Parameters:
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading pair symbol (e.g., "BTCUSDT") |
interval |
CandleInterval |
Candle time interval: "1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h" |
limit |
number |
Maximum number of candles to fetch (fetched backward from execution time) |
Returns: Promise<ICandleData[]> - Array of candle data with timestamp, open, high, low, close, volume
Context Requirements:
ExecutionContextService.runInContextMethodContext with exchangeNameExample:
addStrategy({
strategyName: "ema-crossover",
interval: "1m",
getSignal: async (symbol) => {
// Fetch last 20 candles at 1-minute interval
const candles = await getCandles(symbol, "1m", 20);
// Calculate EMA from candles
const closes = candles.map(c => c.close);
const ema = calculateEMA(closes, 20);
// Generate signal based on indicator
if (ema > threshold) {
return {
position: "long",
priceTakeProfit: candles[0].close * 1.02,
priceStopLoss: candles[0].close * 0.98,
minuteEstimatedTime: 60,
};
}
return null;
}
});
Calculates the Volume-Weighted Average Price (VWAP) from the most recent 1-minute candles. Uses CC_AVG_PRICE_CANDLES_COUNT candles (default: 5) for calculation.
Formula:
VWAP = Σ(Typical Price × Volume) / Σ(Volume)
where Typical Price = (High + Low + Close) / 3
Signature:
function getAveragePrice(symbol: string): Promise<number>
Parameters:
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading pair symbol (e.g., "BTCUSDT") |
Returns: Promise<number> - Volume-weighted average price
Context Requirements:
CC_AVG_PRICE_CANDLES_COUNT 1-minute candlesExample:
addStrategy({
strategyName: "vwap-strategy",
interval: "1m",
getSignal: async (symbol) => {
const vwap = await getAveragePrice(symbol);
const lastCandle = (await getCandles(symbol, "1m", 1))[0];
// Entry if price is below VWAP (potential reversion)
if (lastCandle.close < vwap * 0.995) {
return {
position: "long",
priceTakeProfit: vwap * 1.01,
priceStopLoss: vwap * 0.98,
minuteEstimatedTime: 30,
};
}
return null;
}
});
Formats a price value according to the exchange's precision rules for a specific symbol.
Signature:
function formatPrice(symbol: string, price: number): Promise<string>
Parameters:
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading pair symbol (e.g., "BTCUSDT") |
price |
number |
Raw price value to format |
Returns: Promise<string> - Formatted price string with correct precision
Context Requirements:
MethodContext with exchangeNameformatPrice implementationExample:
addExchange({
exchangeName: "binance",
formatPrice: async (symbol, price) => {
// BTCUSDT: 2 decimal places
// ETHUSDT: 2 decimal places
// DOGEUSDT: 5 decimal places
const precisionMap = {
"BTCUSDT": 2,
"ETHUSDT": 2,
"DOGEUSDT": 5,
};
const precision = precisionMap[symbol] ?? 2;
return price.toFixed(precision);
},
// ... other methods
});
// Usage in strategy or reporting
const formattedPrice = await formatPrice("BTCUSDT", 45123.456789);
// Returns: "45123.46"
Formats a quantity value according to the exchange's precision rules for a specific symbol.
Signature:
function formatQuantity(symbol: string, quantity: number): Promise<string>
Parameters:
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading pair symbol (e.g., "BTCUSDT") |
quantity |
number |
Raw quantity value to format |
Returns: Promise<string> - Formatted quantity string with correct precision
Context Requirements:
MethodContext with exchangeNameformatQuantity implementationExample:
addExchange({
exchangeName: "binance",
formatQuantity: async (symbol, quantity) => {
// BTC: 6 decimal places (min 0.000001)
// ETH: 5 decimal places (min 0.00001)
// DOGE: 0 decimal places (whole numbers)
const precisionMap = {
"BTCUSDT": 6,
"ETHUSDT": 5,
"DOGEUSDT": 0,
};
const precision = precisionMap[symbol] ?? 8;
return quantity.toFixed(precision);
},
// ... other methods
});
// Usage with position sizing
const quantity = 0.00123456789;
const formatted = await formatQuantity("BTCUSDT", quantity);
// Returns: "0.001235"
Retrieves the current execution context timestamp. Returns the historical timestamp during backtest or Date.now() during live trading.
Signature:
function getDate(): Date
Returns: Date - Current execution context timestamp
Context Requirements:
ExecutionContextService.runInContextexecutionContext.whenExample:
addStrategy({
strategyName: "time-based",
interval: "1m",
getSignal: async (symbol) => {
const now = getDate();
const hour = now.getHours();
// Only trade during specific hours
if (hour >= 9 && hour <= 16) {
const candles = await getCandles(symbol, "1m", 1);
return {
position: "long",
priceTakeProfit: candles[0].close * 1.01,
priceStopLoss: candles[0].close * 0.99,
minuteEstimatedTime: 30,
};
}
return null;
}
});
Returns whether the current execution is in backtest mode or live trading mode.
Signature:
function getMode(): boolean
Returns: boolean - true if in backtest mode, false if in live mode
Context Requirements:
ExecutionContextService.runInContextexecutionContext.backtestExample:
addStrategy({
strategyName: "mode-aware",
interval: "1m",
getSignal: async (symbol) => {
const isBacktest = getMode();
if (isBacktest) {
// Use conservative parameters for backtest
const multiplier = 1.01;
} else {
// Use aggressive parameters for live trading
const multiplier = 1.015;
}
const candles = await getCandles(symbol, "1m", 1);
return {
position: "long",
priceTakeProfit: candles[0].close * multiplier,
priceStopLoss: candles[0].close * 0.99,
minuteEstimatedTime: 30,
};
}
});
The ICandleData interface defines the structure returned by getCandles:
| Field | Type | Description |
|---|---|---|
timestamp |
number |
Unix timestamp in milliseconds when candle opened |
open |
number |
Opening price at candle start |
high |
number |
Highest price during candle period |
low |
number |
Lowest price during candle period |
close |
number |
Closing price at candle end |
volume |
number |
Trading volume during candle period |
Exchange functions delegate to the user-defined IExchangeSchema implementation registered via addExchange(). The schema must implement three required methods:
addStrategy({
strategyName: "sma-crossover",
interval: "1m",
getSignal: async (symbol) => {
// Fetch enough candles for both SMAs
const candles = await getCandles(symbol, "1m", 50);
// Calculate SMAs
const closes = candles.map(c => c.close);
const sma20 = closes.slice(0, 20).reduce((a, b) => a + b) / 20;
const sma50 = closes.reduce((a, b) => a + b) / 50;
// Crossover signal
if (sma20 > sma50) {
return {
position: "long",
priceTakeProfit: closes[0] * 1.02,
priceStopLoss: closes[0] * 0.98,
minuteEstimatedTime: 120,
};
}
return null;
}
});
addStrategy({
strategyName: "vwap-reversal",
interval: "5m",
getSignal: async (symbol) => {
const vwap = await getAveragePrice(symbol);
const recentCandles = await getCandles(symbol, "1m", 5);
// Entry when price deviates from VWAP
const currentPrice = recentCandles[0].close;
const deviation = (currentPrice - vwap) / vwap;
if (deviation < -0.01) { // 1% below VWAP
return {
position: "long",
priceTakeProfit: vwap, // Target: return to VWAP
priceStopLoss: currentPrice * 0.98,
minuteEstimatedTime: 60,
};
}
return null;
}
});
addStrategy({
strategyName: "multi-timeframe",
interval: "1m",
getSignal: async (symbol) => {
// Short-term trend (5-minute candles)
const candles5m = await getCandles(symbol, "5m", 12); // 1 hour
const trend5m = candles5m[0].close > candles5m[11].close;
// Long-term trend (1-hour candles)
const candles1h = await getCandles(symbol, "1h", 24); // 1 day
const trend1h = candles1h[0].close > candles1h[23].close;
// Entry only if both timeframes agree
if (trend5m && trend1h) {
const entryPrice = candles5m[0].close;
return {
position: "long",
priceTakeProfit: entryPrice * 1.03,
priceStopLoss: entryPrice * 0.97,
minuteEstimatedTime: 180,
};
}
return null;
}
});
addStrategy({
strategyName: "reporting-strategy",
interval: "1m",
callbacks: {
onClose: async (symbol, signal, priceClose, backtest) => {
// Format prices for logging or external reporting
const formattedOpen = await formatPrice(symbol, signal.priceOpen);
const formattedClose = await formatPrice(symbol, priceClose);
const formattedTP = await formatPrice(symbol, signal.priceTakeProfit);
console.log(`Signal closed:`);
console.log(` Entry: ${formattedOpen}`);
console.log(` Exit: ${formattedClose}`);
console.log(` Target: ${formattedTP}`);
}
},
getSignal: async (symbol) => {
// ... signal logic
}
});
Fetch only the minimum required candles to reduce memory usage and improve performance:
// ❌ Bad: Fetching excessive data
const candles = await getCandles(symbol, "1m", 1000); // 1000 candles
// ✅ Good: Fetch only what's needed
const candles = await getCandles(symbol, "1m", 20); // 20 candles for EMA
Use getAveragePrice() instead of last candle close for more accurate market pricing:
// ❌ Acceptable: Using last candle close
const candles = await getCandles(symbol, "1m", 1);
const entryPrice = candles[0].close;
// ✅ Better: Using VWAP (5-candle average)
const entryPrice = await getAveragePrice(symbol);
Always format prices and quantities before external API calls or logging:
// ❌ Bad: Raw float values
console.log(`Price: ${signal.priceOpen}`); // 45123.456789123
// ✅ Good: Exchange-formatted values
const formatted = await formatPrice(symbol, signal.priceOpen);
console.log(`Price: ${formatted}`); // 45123.46
Use getMode() to adjust behavior between backtest and live:
const isBacktest = getMode();
const limit = isBacktest ? 100 : 20; // More data in backtest
const candles = await getCandles(symbol, "1m", limit);
Exchange functions may throw errors in the following scenarios:
| Error Condition | Cause | Resolution |
|---|---|---|
| Context not established | Functions called outside ExecutionContextService.runInContext |
Ensure functions are called within strategy execution context |
| Exchange not registered | exchangeName in MethodContext does not exist |
Register exchange via addExchange() before execution |
| Invalid interval | Unsupported CandleInterval value |
Use one of the supported intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h |
| Data source failure | Exchange schema's implementation throws error | Implement error handling in exchange schema's methods |
| Network timeout | External API call exceeds timeout | Implement retry logic or timeout handling in schema |
The framework does not cache candle data by default. For strategies that repeatedly access the same candles, consider implementing your own caching layer:
const candleCache = new Map<string, ICandleData[]>();
async function getCachedCandles(symbol: string, interval: CandleInterval, limit: number) {
const key = `${symbol}:${interval}:${limit}`;
if (!candleCache.has(key)) {
candleCache.set(key, await getCandles(symbol, interval, limit));
}
return candleCache.get(key)!;
}
getAveragePrice() fetches CC_AVG_PRICE_CANDLES_COUNT candles and calculates VWAP on every call. For high-frequency strategies, cache the result:
let cachedVWAP: { timestamp: number, value: number } | null = null;
async function getCachedVWAP(symbol: string): Promise<number> {
const now = getDate().getTime();
if (!cachedVWAP || now - cachedVWAP.timestamp > 60000) { // 1 minute cache
cachedVWAP = {
timestamp: now,
value: await getAveragePrice(symbol)
};
}
return cachedVWAP.value;
}