javascript

Как ИИ получил руки для торговли на бирже

  • вторник, 10 февраля 2026 г. в 00:00:04
https://habr.com/ru/articles/994580/

Исходный код, разобранный в статье, размещён по ссылке

На картинах или геральдике Георгий Победоносец всегда с копьём. Но почему копьё? Можно было изобразить лук, мечь, булаву. Длинное древко копья идет в облако в небеса (к начальству): знание и сила работают с инструментом действия как левая и правая нога.

 Метафора c Claude Code прямолинейна. Impossible? Possible.
Метафора c Claude Code прямолинейна. Impossible? Possible.

Именно в этом проблема Claude Code.

Claude Code — это AI-агент от Anthropic для автономной разработки кода. Он обладает сверхчеловеческим распознаванием паттернов, может проанализировать 50MB логов бэктеста за секунды (сам придумав и отладив python скрипт через разработку тестированием), определить почему 80% сделок закрываются по таймауту или SL, и модифицировать Pine Script индикаторы с хирургической точностью.

Но у него нет рук.

Проблема TradingView: узким местом становится человек

Если отбросить маркетинг, реальный процесс создания автоматизированной production торговой стратегии выглядит так. TradingView заменил MetaTrader, торговые стратегии пишут на нём.

  1. Пишем стратегию в веб‑редакторе Pine Script на сайте TradingView.

  2. Запускаем бэктест (лимит ~5000 баров если нет банковской карты).

  3. Скачиваем CSV.

  4. Отправляем в Claude/Grok/DeepSeek

  5. Меняем одну строчку.

  6. Повторяем.

Стоимость итерации слишком высокая из-за кривых рук человека: количество CSV файлов либо превращает папку загрузок в помойку, либо прогон недели времени backtest происходит за день вдумчивой работы так как нужно именовать и размещать по папкам файлы. Человек так физически долго не проработает

А ещё можно передать в чат не тот файл и словить look ahead bias

Архитектурный изъян Pine Script

Язык Pine Script это синтаксический сахар для for loop где переменные объявляются внутри блока и вне блока цикла

// Локально в итерации цикла по свечам
long_cond = ta.crossover(ema_fast, ema_slow) and rsi > 40 and rsi < 65 and trend_up and vol_spike and mom_up 
short_cond = ta.crossunder(ema_fast, ema_slow) and rsi < 60 and rsi > 35 and trend_down and vol_spike and mom_down

// Глобально вне цикла
var int bars_since_signal = 0

Как следствие, если нужно дождаться благоприятных условий на быстром (15m) и долгом (4h) таймфрейме, то получаются костыли

ema200_4h = request.security(
    syminfo.tickerid,
    "240",
    ta.ema(close, 200),
    lookahead = barmerge.lookahead_off
)

Ближайший аналог такого кода из математики - комплексное число. Сама цифра ничего не значит: нужна динамика изменения ряда чисел (в общем растет или падает). А двойные циклы for это неэффективно (квадратичная сложность), поэтому их не запилили. А ещё непонятно что делает barmerge.lookahead_off

// Правильно так?
breakeven = entryPrice * (1 + commissionPct * 2 + slippagePct * 2)

// Или так?
breakeven = entryPrice * (1 + entryFee) / (1 - exitFee - slippage)

// А может так?
trueBreakeven := entryPrice * (1 + entryCommission) / (1 - exitCommission - slippage) * (1 + fundingRate * holdingPeriod / 8)

Ещё веселее дела с математикой безубытка: этот код нечитаем, любая галлюцинации языковой модели визуально неотличимо сделает убыток. При этом, программист будет убежден что проблема в индикаторах

А теперь руки есть

Для того, чтобы исключить человеческий фактор нужно создать систему, которая будет

  1. Запускать Pine Script локально self hosted

    Уже сделано в https://github.com/QuantForgeOrg/PineTS Визуализация индикаторов уже в опенсорсе, торговые стратегии в рамках статьи не интересны

  2. Обеспечить временную синхронизацию вне редактируемого LLM кода

    Разобрано в статье https://habr.com/ru/articles/979116/ Важно вынести критический код из зоны работы ИИ агенты чтобы избежать халтуры или галюцинаций

  3. Одинаковый код в backtest/live/paper

    Разобрано в статье https://habr.com/ru/articles/980736/ Там рассказано о торговых стратегиях на естественном языке, но бесшовная миграция live/paper/dev уже сделана

  4. Вести структурированные JSONL‑логи как разметка нативная LLM агентам

    JSONL (JSON Lines) это формат как для dataset в huggingface, так и для логов. Claude читает их с расширенным лимитом или сам пишет скрипт для быстрого поиска строки

  5. Файл Agent Skills чтобы избежать повторный research на новом контексте чата

    Создается автоматически при запуске команды npx -y @backtest-kit/sidekick. Так же это позволяет попросить обычнай чат Claude использовать код backtest-kit для исследования создав docker контейнер на стороне сервера не трогая машину программиста

Дополнительно, так как всё opensource, это можно запустить на любой бирже, в том числе которой нет в TradingView или компания отключила брокера

Исправляем Pine Script

Чтобы совместить данные разных таймфреймов нужно запустить два отдельных индикатора единомоментно. Полный код программы по ссылке уже содержит механизм кеширования старшено таймфрейма 4h, вычисления оптимизированы

import { addStrategySchema } from "backtest-kit";
import { randomString } from "functools-kit";

import * as math_15m from "../../math/timeframe_15m.math.mjs";
import * as math_4h from "../../math/timeframe_4h.math.mjs";

import StrategyName from "../../enum/StrategyName.mjs";
import RiskName from "../../enum/RiskName.mjs";

addStrategySchema({
  strategyName: StrategyName.MainStrategy,
  interval: "5m",
  getSignal: async (symbol) => {

    const signalId = randomString();
    
    const data_4h = await math_4h.getData(signalId, symbol);
        // await pinescript.run(
        //   File.fromPath("timeframe_4h.pine"), 
        //   { symbol, timeframe: "4h", limit: 100 }
        // )

    if (data_4h.noTrades) {
      return null;
    }

    const data_15m = await math_15m.getData(signalId, symbol);
        // await run(
        //   File.fromPath("timeframe_15m.pine"), 
        //   { symbol, timeframe: "15m", limit: 100 }
        // )

    if (data_15m.position === 0) {
      return null;
    }

    if (data_4h.allowShort && data_15m.position === 1) {
      return null;
    }

    if (data_4h.allowLong && data_15m.position === -1) {
      return null;
    }

    {
      math_15m.dumpPlot(signalId, symbol);
      math_4h.dumpPlot(signalId, symbol);
    }

    return await math_15m.getSignal(signalId, symbol);
  },
  riskList: [
    RiskName.TakeProfitDistanceRisk, 
    RiskName.StopLossDistanceRisk
  ],
});

Как Claude Code использует это

Я не смогу рассказать кратко. На изображениях можно видеть пример выхода из позиции по Келли: цена не достигла TP, но локальный спайк (левый нижний угол) позволил заработать 90% прибыли через выход частями на таймфрейме 15 минут не взирая на то, что дальше цена колебалась в боковике (правый нижний угол) и на часовом таймфрейме начала падение

Claude настраивает это сам на основе логов JSONL
Claude настраивает это сам на основе логов JSONL

Доступен не только технический, но и Мультитаймфреймовый, Фундаментальный анализ. Antropic уже создали и поддерживают инструменты поиска в интернете для Claude, поэтому он сам найдет новости в интернете и, если надо, будет открывать только short. Так же, пакет @backtest-kit/ollama и асинхронная природа функции getSignal позволяют искать новости и прогнозы в интернете на лету не меняя код бота. Но связка Claude Code on the web плюс GitHub Actions значительно удобнее: команду адаптировать стратегию можно дать с iPad находясь в отпуске

Пример создания торгового бота

Человек: Создай крипто-торгового бота используя RSI дивергенцию и подтверждение объёмом

Claude Code:

npx @backtest-kit/sidekick crypto-bot
cd crypto-bot

# Читает сгенерированные Pine Script шаблоны
cat config/source/timeframe_15m.pine

Claude Code: Вижу шаблон с EMA кроссовером. Модифицирую его для RSI дивергенции...

# Редактирует Pine Script стратегию
cat > config/source/rsi_divergence.pine << 'EOF'
//@version=5
indicator("RSI Divergence Strategy")

// Вычисление RSI
rsi = ta.rsi(close, 14)
rsi_prev = ta.rsi(close[1], 14)

// Определение дивергенции цены
bullish_div = close < close[5] and rsi > rsi[5] and rsi < 35
bearish_div = close > close[5] and rsi < rsi[5] and rsi > 65

// Подтверждение объёмом
vol_spike = volume > ta.sma(volume, 20) * 1.5

// Сигналы
long_signal = bullish_div and vol_spike
short_signal = bearish_div and vol_spike

plot(long_signal ? 1 : short_signal ? -1 : 0, "Signal")
plot(close, "Close")
plot(close * 0.98, "StopLoss")   // 2% стоп
plot(close * 1.03, "TakeProfit") // 3% цель
EOF

Claude Code: Запускаю бэктест на октябре 2025 (период крипто-краха)...

npm start -- \
  --mode backtest \
  --frame October2025 \
  --strategy rsi-divergence \
  --symbol BTCUSDT

Результаты бэктеста:

Плохой PNL
Плохой PNL

Claude Code: Хм, убыток во время волатильности. Добавлю динамические стопы на базе ATR и фильтр тренда...

# Модифицирует Pine Script
str_replace_editor \
  --path config/source/rsi_divergence.pine \
  --old_str "plot(close * 0.98, \"StopLoss\")" \
  --new_str "atr = ta.atr(14)\nplot(close - atr * 2, \"StopLoss\")"

# Добавляет 4H фильтр тренда
cat > config/source/trend_filter.pine << 'EOF'
//@version=5
indicator("Trend Filter 4H")

ema200 = ta.ema(close, 200)
bullish_trend = close > ema200
bearish_trend = close < ema200

plot(bullish_trend ? 1 : bearish_trend ? -1 : 0, "TrendBias")
EOF

Claude Code: Перезапускаю бэктест с динамическими стопами + фильтром тренда...

Claude Code: Стратегия теперь прибыльна. Деплою в paper trading для живой валидации...

npm start -- \
  --mode paper \
  --strategy rsi-divergence \
  --symbol BTCUSDT

Спасибо за внимание!