Цель этой статьи — максимально подробно и практично разобрать реальный Python‑проект автоматического трейдинга. Это не концепт, а рабочий бот, который: непрерывно анализирует рынок Binance Futures, ищет сигналы по открытому интересу (Open Interest), применяет набор защитных фильтров, работает с множеством пользователей одновременно, управляется через Telegram‑интерфейс и при необходимости открывает реальные сделки через API биржи BingX.

Далее я последовательно разберу всю логику и все функции основного файла main.py, объясняя, как и зачем они реализованы именно так.

Общая архитектура проекта

Архитектурно проект разделён на два слоя:

  1. Управляющий слой (main.py) — получение данных, расчёт сигналов, фильтры, Telegram‑интерфейс, логика пользователей и принятие решений.

  2. Исполнительный слой (bingx_client.py) — работа с торговым API BingX: подпись запросов, установка плеча, открытие ордеров, трейлинг.

Ключевая идея — вся торговая логика должна быть независима от конкретной биржи. В main.py мы оперируем функции вроде place_market_order(), а не HTTP‑запросами. Клиент для биржи я написал самостоятельно и использую его во всех своих проектах и статьях. Ссылка на файла проекта, включая этот клиент: github

Хранение пользователей и состояния

Бот поддерживает одновременную работу с большим количеством пользователей. Для этого используется простая, но надёжная модель хранения состояния в JSON:

USERS_FILE = Path("users.json")


def load_users():
if USERS_FILE.exists():
return json.loads(USERS_FILE.read_text())
return {}


def save_users(users):
USERS_FILE.write_text(json.dumps(users, indent=4))


users = load_users()

Каждый пользователь — это словарь с настройками торговли, фильтрами, чёрным списком и служебными метаданными. Такой подход позволяет легко сериализовать состояние, не зависеть от БД, безопасно переживать перезапуск процесса.

Вспомогательные функции и утилиты

Процентные изменения

def pct(now, past):
  if past == 0:
  return 0.0
  return (now - past) / past * 100.0

Эта функция используется повсеместно: и для цены, и для OI. Явное вынесение её в утилиту снижает вероятность логических ошибок и делает расчёты единообразными.

Отправка сообщений в Telegram

def send_alert(chat_id, text):
    try:
        bot.send_message(
            chat_id=chat_id,
            text=text,
            parse_mode="HTML"
        )
    except Exception as e:
        print(f"Telegram error {chat_id}: {e}")

Работа с Binance Futures API

Данные рынка получаются исключительно через публичные эндпоинты Binance Futures:

def binance_get(endpoint, params=None):
  url = BINANCE_FAPI_URL + endpoint
  r = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
  r.raise_for_status()
  return r.json()

Поверх неё реализованы функции, работающие с апи. Например:

def get_symbols():
  data = binance_get("/fapi/v1/exchangeInfo")
  return [
  s["symbol"]
  for s in data["symbols"]
  if s["contractType"] == "PERPETUAL"
  and s["quoteAsset"] == "USDT"
  and s["status"] == "TRADING"
  ]

Здесь мы получаем все фьючерсные символы, которые торгуются к валюте USDT. Это позволят перебирать только релевантные активы.

Логика OI‑сигнала

Основная аналитика сосредоточена в функции check_symbol.

oi_4h = get_oi_hist(symbol, 48)
oi_24h = get_oi_hist(symbol, 288)

48 и 288 точек соответствуют 4 и 24 часам при таймфрейме 5 минут. Это делает расчёт полностью детерминированным.

Далее извлекаются крайние значения и рассчитываются проценты:

oi_now = float(oi_4h[-1]["sumOpenInterestValue"])
oi_4h_ago = float(oi_4h[0]["sumOpenInterestValue"])
oi_growth_4h = pct(oi_now, oi_4h_ago)

Сигнал формируется не только по росту OI, но и по соотношению с ценой с помощью коэффициента PRICE_OI_RATIO = 0,5:

signal_4h = (
oi_growth_4h >= OI_4H_THRESHOLD and
price_growth_4h <= oi_growth_4h * PRICE_OI_RATIO
)

Это принципиально: стратегия ищет накопление, а не импульс.

Анти‑спам и защита от переоткрытия

Каждый пользователь имеет словарь last_signal_time:

last_signals = user_data.get("last_signal_time", {})
if symbol in last_signals:
  if datetime.utcnow() - datetime.fromisoformat(last_signals[symbol]) < timedelta(hours=SIGNAL_COOLDOWN_HOURS):
    continue

Этот механизм предотвращает повторные входы по одному инструменту и делает автотрейдинг более контролируемым.

Фильтры: объём и ликвидность

Минимальный OI

if oi_now < MIN_OI_USDT:
  return

Этот фильтр встроен в код. В целом, его основная цель - отсеять неверные данные и совсем низколиквидные монетки, которые иногда могут затесаться на binance.

Фильтр объёма

def check_volume_filter(symbol, multiplier):
  klines = get_klines(symbol, Vol_period)
  volumes = [float(k[5]) for k in klines[:-1]]
  avg_volume = sum(volumes) / len(volumes)
  current_volume = float(klines[-1][5])
  return current_volume >= avg_volume * multiplier

Этот фильтр позволяет торговать только в моменты повышенной активности рынка. На практике при тестировании он действительно кратко уменьшил количество сделок и повысил винрейт в любых рыночных условиях. Его отключение уводило стратегию в убыток, так что такой фильтр отлично себя показывает в данной ТС. Каждый пользователь может поставить персональный мультипликатор (multiplier) в настройках.

Чёрный список (blacklist)

Каждый пользователь может управлять списком запрещённых инструментов с помощью команд /blacklist_add и /blacklist_remove, а также смотреть символы в /blacklist_show.

def blacklist_add(update: Update, context):
    chat_id = str(update.effective_chat.id)

    if not context.args:
        update.message.reply_text("Использование: /blacklist_add BTCUSDT")
        return

    symbol = context.args[0].upper()

    users[chat_id].setdefault("blacklist", [])
    if symbol not in users[chat_id]["blacklist"]:
        users[chat_id]["blacklist"].append(symbol)
        save_users(users)

    update.message.reply_text(f"⛔ {symbol} добавлен в чёрный список")

def blacklist_remove(update: Update, context):
    chat_id = str(update.effective_chat.id)

    if not context.args:
        update.message.reply_text("Использование: /blacklist_remove BTCUSDT")
        return

    symbol = context.args[0].upper()

    if symbol in users[chat_id].get("blacklist", []):
        users[chat_id]["blacklist"].remove(symbol)
        save_users(users)

    update.message.reply_text(f"✅ {symbol} удалён из чёрного списка")

def blacklist_show(update: Update, context):
    chat_id = str(update.effective_chat.id)
    blacklist = users.get(chat_id, {}).get("blacklist", [])

    if not blacklist:
        update.message.reply_text("? Чёрный список пуст")
        return

    text = "<b>⛔ Чёрный список:</b>\n\n"
    text += "\n".join(f"• {s}" for s in sorted(blacklist))

    update.message.reply_text(text, parse_mode="HTML")

Проверка перед сделкой:

if symbol in user_data.get("blacklist", []):
  continue

Это важнейший элемент риск‑менеджмента при реальной торговле.

Переход от сигнала к сделке

qty = (margin_usdt * leverage) / price_now
stop_price = price_now * (1 - stop_loss_pct / 100)
tp_price = price_now * (1 + take_profit_pct / 100)

Все параметры сделки вычисляются исходя из настроек каждого юзера

Открытие сделки делаем с помощью функции bingx_client. Все сделки будут в лонг, так как OI лонг сигналы работают лучше шортовых. Так что пераметр long передаём явно, остальные вычисляли ранее (s - символ на бирже bingx):

resp = bx.place_market_order('long', qty, s, stop_price, tp_price)

Telegram‑интерфейс и кнопки

Меню настроек реализовано через inline‑кнопки:

keyboard = [
[InlineKeyboardButton("Торговля", callback_data='toggle_trading')],
[InlineKeyboardButton("Плечо", callback_data='set_leverage')]
]

Каждая кнопка обрабатывается единым обработчиком:

def button_handler(update, context):
  data = query.data
  if data == 'toggle_trading':
  users[chat_id]['trading_enabled'] = not users[chat_id]['trading_enabled']

Это позволяет легко работать с масштабирование интерфейса, логика понятная и простая. Также такая логика не перегружает код.

Универсальная обработка пользовательского ввода

def set_value(update, context, key, type_func=str):
  value = type_func(update.message.text.strip())
  users[chat_id][key] = value
  save_users(users)

Благодаря этому добавление нового параметра требует минимального количества кода.

Основной цикл работы

for symbol in symbols:
  check_symbol(symbol)
  time.sleep(0.15)

Функция check_symbols выполняет абсолютно всю логику - проверка OI, применение фильтров, проверка на blacklist и открытие сделок при наличии сигнала. Таким образом, мы перебираем все binance символы и открываем сделки на бирже с помощью готового клиента. Я выбрал биржей для открытия bingX из-за комиссий, тем более что binance в России сейчас имеет проблемы.

Заключение

Надеюсь, что у меня получилось объяснить основную логику кода и функций. Всё что вам нужно, чтобы запустить этот проект - получить токен в @botfather и вставить его в переменную BOT_TOKEN. Напоминаю, что весь код я выложил на github.
Данный бот показывает среднюю прибыль в течение 2 недель около 2% в среднем. Были как удачные дни, так и не очень удачные, в которые амплитуда депозита могла быть и более 5% в обе стороны. Так что используйте бота осторожно. Он имеет встроенную функцию работы с testnet, обязательно тестируйте свои настройки сначала там! Я имею следующие настройки сейчас:

Этот проект показывает, как выглядит реальный автоматический трейдинг на Python: с фильтрами, защитой, пользовательским управлением и строгим разделением логики. Именно такой уровень детализации позволяет использовать автотрейдинг качественно.
Всем успехов! Надеюсь что этот продукт будет способен в итоге принести реальную прибыль и вам.

Комментарии (6)


  1. Ibirseven
    03.01.2026 12:23

    Как же у вас всё просто, все данные по пользователю и его настройкам хранятся в одном словаре?) где проверка на существование текущей позиции, чтобы не открывать новую? Что с уровнями ТП и СЛ, устанавливаться сразу при открытии позиции? В общем вопросов куча.


    1. negrbluad Автор
      03.01.2026 12:23

      ну так посмотрите код! Специально оставил ссылку.
      1. В bingx_client стоп и тейк устанавливаются сразу в позицию. Позиция не откроется если с ними будет что-то не так - просто выдаст ошибку по всей позиции

      2.Обратите внимание на словарь last_trade_time - он создаётся по каждому юзеру для позиций. Это очень важный момент, эта проверка не даст открыть новую позицию. Поэтому смысл проверки позиций отпадает
      3. Может возникнуть вопрос - а что если позиция по окончании кулдауна открыта? И тогда произойдёт действительно докуп. Но только при формировании НОВОГО сигнала. Такой докуп чаще всего приводит к тейкам, т.е. к повышенной прибыли.


  1. anyagixx
    03.01.2026 12:23

    Все пи**шь) простите, но абсолютно все потуги это слив депозита


    1. negrbluad Автор
      03.01.2026 12:23

      спасибо за объективную оценку! Сразу видно, что ты алготрейдер с многолетним опытом!


  1. gkaliostro8
    03.01.2026 12:23

    Идея очень хорошая, но после накопления импульс может быть в любую сторону. А что если добавить дивергенцию, и тогда процент профита увеличится


    1. negrbluad Автор
      03.01.2026 12:23

      благодарю за оценку)
      Частично вы, безусловно, правы. Но на практике чаще мы видим всё таки, что рост OI сопряженный с ростом цены указывает именно на продолжение движения. Дивергенции бы кратко снизили количество сделок, но на самом деле повысили их качество.
      В целом довольно много фильтров можно внедрить, даже те же классические трендовые индикаторы