Layer 4: Shared Utilities
These modules are imported by every scanner. They handle common operations so engines don’t duplicate code.
core/signals.mjs — Technical Indicators
Exports: calculateRSI, calculateEMA, calculateSlope, calculateATR, detectCross
RSI (Relative Strength Index)
import { calculateRSI } from '../core/signals.mjs';
const closes = candles.map(c => c.close);
const rsiArray = calculateRSI(closes, 14); // Period 14
const currentRSI = rsiArray[rsiArray.length - 1];
EMA (Exponential Moving Average)
import { calculateEMA } from '../core/signals.mjs';
const ema20 = calculateEMA(closes, 20);
const ema50 = calculateEMA(closes, 50);
Slope (Linear Regression)
import { calculateSlope } from '../core/signals.mjs';
const slope = calculateSlope(closes.slice(-10)); // Last 10 candles
// Returns: { slope, intercept, r2 }
ATR (Average True Range)
import { calculateATR } from '../core/signals.mjs';
const atr = calculateATR(candles, 14); // Period 14
core/bitget_rest.mjs — REST Client
Export: bitgetGet(path)
Usage
import { bitgetGet } from '../core/bitget_rest.mjs';
// Fetch tickers
const tickers = await bitgetGet('/api/v2/mix/market/tickers?productType=USDT-FUTURES');
// Fetch candles
const candles = await bitgetGet(
`/api/v2/mix/market/candles?symbol=BTCUSDT&granularity=5m&limit=50&productType=USDT-FUTURES`
);
Features
- Automatic JSON parsing
- 429 retry with exponential backoff (2s → 4s → 8s)
- 10-second timeout
- Error logging
core/telegram_notifier.mjs — Telegram Alerts
Export: sendTelegram(text, options)
Usage
import { sendTelegram } from '../core/telegram_notifier.mjs';
await sendTelegram(`🚀 BTCUSDT LONG detected!`, { parse_mode: 'HTML' });
Configuration
Reads Telegram bot token from /root/.openclaw/openclaw.json:
{
"channels": {
"telegram": {
"botToken": "8649740305:AAHhI1huuNp-gvmEVenTk3p2L__BkNsdGh8",
"allowFrom": ["1948260663"]
}
}
}
Features
- HTML formatting supported
- Auto-retry on failure
- Logs success/failure
core/signal_logger.mjs — HyperOpt Signal Logging
Export: logSignal(signalObject)
Usage
import { logSignal } from '../core/signal_logger.mjs';
logSignal({
engine: 'my_scanner',
symbol: 'BTCUSDT',
side: 'LONG',
price: 65000,
tpPct: 0.06, // 6% take profit
slPct: 0.03, // 3% stop loss
metadata: {
rsi1h: 45,
score: 5,
confidence: 'HIGH'
}
});
What It Does
- Appends to
/app/trading_engine/data/all_signals.jsonl - Writes to
/app/trading_engine/data/hyperopt_training_data.jsonl - Captures regime + coinGroup automatically
- Every signal gets a unique
signalKey
Output Format (all_signals.jsonl)
{
"timestamp": "2026-04-27T10:00:00.000Z",
"unix_ts": 1714212000000,
"signalKey": "1714212000000_my_scanner_BTCUSDT_LONG",
"engine": "my_scanner",
"symbol": "BTCUSDT",
"side": "LONG",
"price": 65000,
"tpPct": 0.06,
"slPct": 0.03,
"regime": {
"market": "trending",
"confidence": 0.75,
"adx": 28,
"atrPct": 2.5,
"btcTrend": "up"
},
"coinGroup": "large-cap",
"metadata": { "rsi1h": 45, "score": 5 }
}
Rule
Every engine MUST call logSignal() on every signal. This is mandatory for HyperOpt.
core/pump_guard.mjs — Pump Protection
Export: isPumpActive(symbol)
What It Does
Prevents SHORT signals from firing when a coin is actively pumping. Reads pump detector signals and cooldowns.
Usage
import { isPumpActive } from '../core/pump_guard.mjs';
if (side === 'SHORT' && await isPumpActive(symbol)) {
console.log(`[PUMP GUARD] Blocking SHORT for ${symbol} — active pump detected`);
return; // Skip signal
}
How It Works
- Checks
pump_detector_signals.jsonfor recent pump signals - Checks cooldown file for active pump state
- Returns
trueif pump detected within last 4 hours - Only guards SHORT signals (LONGs benefit from pumps)
Integration Points
7+ engines use pump_guard: dump_detector, dump_v2, dump_v3, distribution_top, gravity, exhaustion, oversold_bounce
core/position_registry.mjs — Position Tracking
Export: checkPosition(symbol), registerPosition(symbol, side), clearPosition(symbol)
What It Does
Shared state across all engines. Prevents opposing positions on the same coin.
Usage
import { checkPosition, registerPosition } from '../core/position_registry.mjs';
// Before firing a signal
const existing = checkPosition(symbol);
if (existing && existing.side !== side) {
console.log(`[REGISTRY] ${symbol} already has ${existing.side} position, skipping ${side}`);
return;
}
// After firing a signal
registerPosition(symbol, side);
Auto-Cleanup
Positions auto-clear after 24 hours. File: /app/trading_engine/data/position_registry.json
Import Summary
// Every scanner imports these:
import { readCandleCache } from '../core/candle_cache.mjs';
import { calculateRSI, calculateEMA, calculateSlope } from '../core/signals.mjs';
import { bitgetGet } from '../core/bitget_rest.mjs';
import { sendTelegram } from '../core/telegram_notifier.mjs';
import { logSignal } from '../core/signal_logger.mjs';
import { isPumpActive } from '../core/pump_guard.mjs';
import { checkPosition, registerPosition } from '../core/position_registry.mjs';