Разработка скринера по объемам торгов

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка скринера по объемам торгов
Средний
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1122
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    859

Разработка скринера по объёмам

Скринер по объёмам — это инструмент для поиска инструментов с аномальной торговой активностью. Резкий рост объёма часто предшествует крупному движению цены. Профессиональные трейдеры используют volume screener для поиска торговых возможностей до того, как движение стало очевидным.

Метрики объёма

Volume Ratio: текущий объём / средний объём за N периодов. Ratio > 3 — аномальный объём.

Relative Volume (RVOL): объём текущей свечи относительно среднего объёма для этого времени суток. RVOL > 2 в 14:00 значит: сегодня в 14:00 объём в 2× больше чем обычно в это время.

Volume Spike: разовый всплеск объёма в одной свече — часто сигнал крупного участника.

OBV (On Balance Volume): накопительный индикатор. Накопление = умные деньги входят. Распределение = умные деньги выходят.

Архитектура скринера

interface VolumeScreenerItem {
  symbol: string;
  exchange: string;
  currentVolume: number;
  avgVolume20: number;       // средний за 20 периодов
  volumeRatio: number;       // current / avg20
  rvol: number;              // relative to same-hour average
  volumeDelta: number;       // объём buy - sell (если есть данные)
  volumeTrend: 'increasing' | 'decreasing' | 'spike';
  priceChange: number;       // % за период
  volumePrice: 'confirming' | 'diverging';  // объём подтверждает ценовое движение?
}

Расчёт объёмных метрик

function calculateVolumeMetrics(
  candles: OHLCV[],
  currentCandle: OHLCV
): VolumeMetrics {
  const period = 20;
  const recentCandles = candles.slice(-period);
  
  // Средний объём
  const avgVolume = recentCandles.reduce((sum, c) => sum + c.volume, 0) / period;
  
  // Volume Ratio
  const volumeRatio = currentCandle.volume / avgVolume;
  
  // Тренд объёма: линейная регрессия за последние 5 свечей
  const recentVolumes = candles.slice(-5).map(c => c.volume);
  const volumeTrendSlope = linearRegressionSlope(recentVolumes);
  
  // Подтверждение: цена вверх + объём вверх = bullish confirmation
  const priceChange = (currentCandle.close - candles.slice(-2)[0].close) / candles.slice(-2)[0].close;
  const volumeChange = currentCandle.volume / candles.slice(-2)[0].volume - 1;
  
  const confirming = (priceChange > 0 && volumeChange > 0) || (priceChange < 0 && volumeChange > 0);
  
  return {
    avgVolume,
    volumeRatio,
    volumeTrend: volumeTrendSlope > 0.1 ? 'increasing' : volumeTrendSlope < -0.1 ? 'decreasing' : 
                 (volumeRatio > 3 ? 'spike' : 'normal'),
    volumePrice: confirming ? 'confirming' : 'diverging',
  };
}

function linearRegressionSlope(values: number[]): number {
  const n = values.length;
  const xMean = (n - 1) / 2;
  const yMean = values.reduce((a, b) => a + b) / n;
  
  let numerator = 0;
  let denominator = 0;
  
  for (let i = 0; i < n; i++) {
    numerator += (i - xMean) * (values[i] - yMean);
    denominator += (i - xMean) ** 2;
  }
  
  return denominator ? numerator / denominator : 0;
}

Сбор данных для множества пар

class VolumeDataCollector {
  private candleCache = new Map<string, OHLCV[]>();
  private exchange: ccxt.Exchange;
  
  async fetchAllCandles(symbols: string[], timeframe: string): Promise<void> {
    // Параллельный запрос, соблюдая rate limits
    const chunks = chunkArray(symbols, 10);  // по 10 запросов параллельно
    
    for (const chunk of chunks) {
      await Promise.all(
        chunk.map(async (symbol) => {
          const candles = await this.exchange.fetchOHLCV(symbol, timeframe, undefined, 100);
          this.candleCache.set(`${symbol}:${timeframe}`, candles.map(formatCandle));
        })
      );
      await sleep(100);  // небольшая пауза между чанками
    }
  }
  
  async getScreenerData(timeframe: string, minVolumeRatio: number = 2): Promise<VolumeScreenerItem[]> {
    const results: VolumeScreenerItem[] = [];
    
    for (const [key, candles] of this.candleCache) {
      if (!key.endsWith(`:${timeframe}`)) continue;
      const symbol = key.split(':')[0];
      
      if (candles.length < 21) continue;
      
      const metrics = calculateVolumeMetrics(candles.slice(0, -1), candles[candles.length - 1]);
      
      if (metrics.volumeRatio >= minVolumeRatio) {
        results.push({
          symbol,
          currentVolume: candles[candles.length - 1].volume,
          ...metrics,
        });
      }
    }
    
    return results.sort((a, b) => b.volumeRatio - a.volumeRatio);
  }
}

UI скринера

function VolumeScreener() {
  const [timeframe, setTimeframe] = useState('1h');
  const [minRatio, setMinRatio] = useState(2);
  const [sortBy, setSortBy] = useState<'volumeRatio' | 'currentVolume'>('volumeRatio');
  const [data, setData] = useState<VolumeScreenerItem[]>([]);
  
  // Обновляем каждые 5 минут
  useEffect(() => {
    const interval = setInterval(() => refreshScreener(), 5 * 60 * 1000);
    return () => clearInterval(interval);
  }, [timeframe, minRatio]);
  
  return (
    <div>
      <Controls
        timeframe={timeframe} onTimeframeChange={setTimeframe}
        minRatio={minRatio} onMinRatioChange={setMinRatio}
      />
      
      <table>
        <thead>
          <tr>
            <th>Symbol</th>
            <th onClick={() => setSortBy('volumeRatio')}>Vol Ratio ↕</th>
            <th>RVOL</th>
            <th>Volume ($)</th>
            <th>Price Change</th>
            <th>Trend</th>
            <th>Confirmation</th>
          </tr>
        </thead>
        <tbody>
          {data.map(item => (
            <VolumeRow key={item.symbol} item={item} />
          ))}
        </tbody>
      </table>
    </div>
  );
}

const VolumeRow: React.FC<{ item: VolumeScreenerItem }> = ({ item }) => (
  <tr className={item.volumeRatio > 5 ? 'highlight-spike' : ''}>
    <td><a href={`/trade/${item.symbol}`}>{item.symbol}</a></td>
    <td>
      <VolumeRatioBar ratio={item.volumeRatio} />
      <span>{item.volumeRatio.toFixed(1)}x</span>
    </td>
    <td>{item.rvol.toFixed(1)}x</td>
    <td>{formatVolume(item.currentVolume)}</td>
    <td className={item.priceChange > 0 ? 'green' : 'red'}>
      {item.priceChange > 0 ? '+' : ''}{item.priceChange.toFixed(2)}%
    </td>
    <td><TrendIcon trend={item.volumeTrend} /></td>
    <td>
      <span className={item.volumePrice === 'confirming' ? 'green' : 'yellow'}>
        {item.volumePrice === 'confirming' ? '✓ Confirm' : '⚡ Diverge'}
      </span>
    </td>
  </tr>
);

Алерты

interface VolumeAlert {
  symbol: string;
  minVolumeRatio: number;  // триггер при достижении
  notifyVia: 'telegram' | 'webhook' | 'email';
}

async function checkVolumeAlerts(screenerData: VolumeScreenerItem[], alerts: VolumeAlert[]) {
  for (const alert of alerts) {
    const item = screenerData.find(d => d.symbol === alert.symbol);
    if (!item) continue;
    
    if (item.volumeRatio >= alert.minVolumeRatio) {
      await sendAlert(alert.notifyVia, {
        message: `Volume spike on ${item.symbol}! Ratio: ${item.volumeRatio.toFixed(1)}x avg | Price: ${item.priceChange > 0 ? '+' : ''}${item.priceChange.toFixed(2)}%`,
      });
    }
  }
}

Разработка volume screener с поддержкой нескольких таймфреймов, алертами и мультибиржевым охватом: 4–6 недель.