Skip to the content.

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


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


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

  1. Appends to /app/trading_engine/data/all_signals.jsonl
  2. Writes to /app/trading_engine/data/hyperopt_training_data.jsonl
  3. Captures regime + coinGroup automatically
  4. 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

  1. Checks pump_detector_signals.json for recent pump signals
  2. Checks cooldown file for active pump state
  3. Returns true if pump detected within last 4 hours
  4. 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';