Розробка скринера за обсягами торгів

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску 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);
  
  // Підтвердження: ціна вгору + обсяг вгору = бичаче підтвердження
  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);
  }
}

Інтерфейс скринера

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 тижнів.