Привет, Habr! В этой статье я хочу поделиться своим проектом — Telegram-ботом, который автоматизирует торговлю на бирже Bybit на основе сигналов из специализированного канала. Бот парсит сообщения из Telegram-канала @TokenSplashBybit, извлекает информацию о предстоящих "token splash" (это события, когда новые токены добавляются на биржу с возможностью получения airdrop), и открывает длинные позиции (лонги) в момент результата. Почему лонги? Потому что token splash на Bybit часто сопровождаются airdrop-вознаграждениями для держателей позиций, многие трейдеры начинают шортить подобные позиции - тем более учитывая, что часто на токены существуют много разных мероприятий, например, binance alpha и прочие. Толпа почти никогда не зарабатывает - так подобных трейдеров почти всегда отвозят наверх, ликвидируя и собирая стопы, что делает стратегию прибыльной в долгосрочной перспективе. Я не даю финансовых советов — это просто технический проект для энтузиастов автоматизации и криптотрейдинга.
Я собрал небольшую статистику вручную на основе исторических данных, чтобы показать потенциальную работоспособность подхода. Конечно, прошлые результаты не гарантируют будущих, и торговля всегда связана с рисками. Но всё же работаю с этим кодом уже не один месяц, и результат действительно соответствует ожиданиям. Давайте разберёмся по порядку: от идеи до полного кода с объяснениями.
Что такое Token Splash на Bybit и почему это выгодно?
Token Splash — это событие на Bybit, когда новый токен добавляется в листинг, и биржа раздаёт airdrop (бесплатные токены) участникам. Часто это связано с фьючерсами на USDT, где открытие позиции в лонг позволяет получить долю от airdrop. Канал @TokenSplashBybit публикует анонсы с токенами и датой "result" (моментом, когда токен становится доступным для торговли).
Логика стратегии проста:
- Парсим канал на новые анонсы. 
- Планируем открытие позиции на дату result (используя scheduler). 
- Открываем лонг на 70% от расчётного объёма с рыночным ордером. 
- Устанавливаем тейк-профиты (TP1 на +3%, TP2 на +6%) и стоп-лосс (SL на -2%). Можно изменять тейки и стопы, но пока что меня устраивают результаты с такими переменными - тем более что они обусловлены анализом результатов всех этих мероприятий. 
- Это позволяет захватить рост цены после листинга и получить airdrop. 
Почему автоматизация? Ручная торговля требует постоянного мониторинга, а бот делает всё сам: от парсинга до размещения ордеров через API Bybit.
Статистика: Подтверждение работоспособности
Чтобы показать, что подход имеет смысл, я вручную собрал данные по нескольким token splash за последний период. Вот часть таблицы с примерами:

Выводы:
- В 70% случаев цена растёт на 3–10% в первые минуты. 
- Редко (15%) — безоткатное падение. Это ловит стоп-лосс. 
Значит, стратегия простая: Входим в лонг → ставим TP на +3% и +6% → ставим SL на -2% . Это оптимальные параметры, при которых мы получим наилучший винрейт.
Логика бота: Шаг за шагом
Бот состоит из нескольких частей:
- Telegram-бот (Telebot): Интерфейс для пользователей. Позволяет включать/выключать бота, настраивать API-ключи Bybit, плечо (leverage) и маржу (margin). Данные хранятся в JSON-файлах. 
- Парсинг канала (Telethon): Асинхронный клиент для Telegram. Парсит сообщения по regex, извлекает токен и дату result. Если дата в будущем — планирует задачу. 
- Планировщик (APScheduler): Запускает функцию notify_all_enabled_users в момент result, которая вызывает long_token для каждого активного пользователя. 
- 
Торговля (Pybit): В long_token: - Получает цену и параметры инструмента. 
- Рассчитывает объём: qty = (leverage * margin) / price, корректирует по шагу (qtyStep). 
- Проверяет баланс USDT. 
- Размещает ордера: Market Buy на 70%, Limit Sell TP1/TP2, StopMarket SL. 
 
- Обработка ошибок: Логирование, уведомления в Telegram при неудачах. 
Бот работает в многопоточном режиме: Telebot и Telethon в отдельных потоках, scheduler в фоне.
Объяснение кода
Давайте разберём ключевые части кода.
Импорты и настройки
import re
from datetime import datetime
import pytz
from telethon import TelegramClient, events
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.date import DateTrigger
from pybit.unified_trading import HTTP
import json
import os
import telebot
from decimal import Decimal
import asyncio
import threading
import time
import logging
# Настройка логирования
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
)
TOKEN = "" 
bot = telebot.TeleBot(TOKEN)
API_ID = 
API_HASH = ''
SESSION_NAME = "my_session"
CHANNEL = '@TokenSplashBybit'
# Регулярка для парсинга токена и Result даты
POST_REGEX = r'^(?P<token>\w+)\n.*?Result (?P<result_date>\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}) UTC'
TRADE_AMOUNT = 1000  # USDT (unused; margin from settings)
TP1_PCT = 0.03
TP2_PCT = 0.06
STOP_LOSS_PCT = 0.02
user_states = {}
USER_DATA_PATH = "user_data_tg"
os.makedirs(USER_DATA_PATH, exist_ok=True)- telebot — создаёт бота в Telegram (кнопки, сообщения). 
- telethon — читает канал (как "глаза" бота). 
- pybit — торгует на Bybit (открывает позиции). 
- apscheduler — запускает торговлю в нужное время. 
Регулярное выражение (regex) — это "фильтр", который находит в тексте:
TOKEN
...
Result 15.10.2025 14:30 UTC→ и вытаскивает TOKEN и 14:30.
Хранение данных пользователей
# GET USER + SAVE USER INFO
def get_user_file(user_id):
    return os.path.join(USER_DATA_PATH, f"{user_id}.json")
def load_user_data(user_id):
    try:
        with open(get_user_file(user_id), 'r') as f:
            return json.load(f)
    except:
        return {}
def save_user_data(user_id, data):
    try:
        with open(get_user_file(user_id), 'w') as f:
            json.dump(data, f)
    except Exception as e:
        logging.error(f"Failed to save user data for {user_id}: {e}")
def get_all_user_ids():
    user_ids = []
    for filename in os.listdir(USER_DATA_PATH):
        if filename.endswith(".json"):
            try:
                user_id = int(filename.split(".")[0])
                user_ids.append(user_id)
            except:
                continue
    return user_ids
def notify_all_enabled_users(token):
    user_ids = get_all_user_ids()
    for uid in user_ids:
        data = load_user_data(uid)
        if data.get("bot_enabled"):
            long_token(uid, token_symbol=token)- Данные (API key, secret, leverage, margin, bot_enabled) хранятся в JSON по user_id. 
- notify_all_enabled_users вызывает торговлю для активных пользователей. 
Каждый пользователь — это папка с файлом 123456.json вида:
{
  "api_key": "abc123",
  "secret": "xyz789",
  "leverage": "10",
  "margin": "20",
  "bot_enabled": true
}- leverage — плечо (10x = 10). 
- margin — сколько USDT тратить на сделку. 
- bot_enabled — включён ли бот. 
Бот может работать для многих людей одновременно.
Меню и обработчики Telebot
def get_menu_markup(user_id):
    data = load_user_data(user_id)
    bot_enabled = data.get("bot_enabled", False)
    state_button = " Бот включен" if bot_enabled else " Бот выключен"
    markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.row(state_button, "ℹ️ Информация о боте")
    markup.row("⚙️ Настройки")
    return markup
settings_markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
settings_markup.row("API Key", "secret")
settings_markup.row("Leverage", "Margin")
settings_markup.row("Назад")
# Вспомогательные функции для безопасного преобразования чисел
def safe_float(value, default=0.0):
    """Безопасное преобразование в float"""
    if value is None:
        return default
    try:
        if isinstance(value, str) and value.strip() == '':
            return default
        return float(value)
    except (ValueError, TypeError):
        return default
def safe_int(value, default=0):
    """Безопасное преобразование в int"""
    if value is None:
        return default
    try:
        if isinstance(value, str) and value.strip() == '':
            return default
        return int(float(value))
    except (ValueError, TypeError):
        return default
# MATH
def count_decimal_places(number: float) -> int:
    s = f"{number:.16f}".rstrip('0')
    if '.' in s:
        return len(s.split('.')[1])
    else:
        return 0
# КОМАНДЫ ДЛЯ БОТА
@bot.message_handler(func=lambda msg: msg.text in [" Бот включен", " Бот выключен"])
def toggle_bot_state(message):
    user_id = message.chat.id
    data = load_user_data(user_id)
    current_state = data.get("bot_enabled", False)
    data["bot_enabled"] = not current_state
    save_user_data(user_id, data)
    
    status = "включен" if data["bot_enabled"] else "выключен"
    bot.send_message(user_id, f"Бот теперь {status}.", reply_markup=get_menu_markup(user_id))
@bot.message_handler(func=lambda msg: msg.text == "ℹ️ Информация о боте")
def bot_info(message):
    user_id = message.chat.id
    info_text = (
        "Это торговый бот для биржи Bybit, торгующий в лонг на токен сплэшах.\n"
        "Вы можете включать и выключать бота, а также настраивать параметры в разделе Настройки. Для грамотной работы бота необходимо обязательно указать всё.\n"
        "Если нужна помощь — обращайтесь @perpetual_god."
    )
    bot.send_message(user_id, info_text, reply_markup=get_menu_markup(user_id))
@bot.message_handler(commands=['start'])
def send_welcome(message):
    user_id = message.chat.id
    bot.send_message(user_id, "Добро пожаловать! Выберите действие:", reply_markup=get_menu_markup(user_id))
@bot.message_handler(func=lambda msg: msg.text in ["⚙️ Настройки"])
def handle_settings_menu(message):
    user_id = message.chat.id
    bot.send_message(user_id, "Выберите параметр:", reply_markup=settings_markup)
@bot.message_handler(func=lambda msg: msg.text in ["API Key", "secret", "Leverage", "Margin"])
def ask_for_value(message):
    user_id = message.chat.id
    key = message.text.lower().replace(" ", "_")
    
    data = load_user_data(user_id)
    current_value = data.get(key, "(не задано)")
    
    user_states[user_id] = key
    bot.send_message(user_id, f"Текущее значение {message.text}: {current_value}\n\nВведите новое значение:")
@bot.message_handler(func=lambda msg: msg.text == "Назад")
def back_to_menu(message):
    user_id = message.chat.id
    bot.send_message(user_id, "Вы вернулись в меню", reply_markup=get_menu_markup(user_id))
# ОБРАБОТЧИК ДЛЯ ВВОДА ЗНАЧЕНИЙ
@bot.message_handler(func=lambda msg: msg.chat.id in user_states)
def catch_input(message):
    user_id = message.chat.id
    if user_id in user_states:
        key = user_states.pop(user_id)
        data = load_user_data(user_id)
        data[key] = message.text.strip()
        save_user_data(user_id, data)
        bot.send_message(user_id, f"{key} сохранено. Выберите следующий параметр:", reply_markup=settings_markup)Вы видите кнопки:
? Бот включён | ℹ️ Инфо | ⚙️ Настройки- Нажали "Настройки" → выбираете "Leverage" → вводите 10. 
- Бот сохраняет и говорит: "Leverage сохранено". 
Это как личный кабинет, но в чате.
Парсинг и планирование
async def fetch_new_tokens(client):
    new_tokens = []
    try:
        logging.debug("[Telethon] Получаем последние 50 сообщений из канала...")
        messages = await client.get_messages(CHANNEL, limit=50)
        logging.info(f"[Telethon] Всего сообщений получено: {len(messages)}")
        for msg in messages:
            if msg.text is None:
                continue
            match = re.search(POST_REGEX, msg.text, re.DOTALL)
            if not match:
                continue
            symbol = match.group('token').strip()
            result_date_str = match.group('result_date').strip()
            try:
                result_dt = datetime.strptime(result_date_str, "%d.%m.%Y %H:%M")
                result_dt = pytz.utc.localize(result_dt)
            except ValueError:
                logging.error(f"[Telethon] Невозможно разобрать дату: {result_date_str}")
                continue
            now = datetime.utcnow().replace(tzinfo=pytz.utc)
            if result_dt > now:
                logging.info(f"[Telethon] Найден токен для планирования: {symbol} (Result: {result_dt})")
                new_tokens.append({
                    "symbol": symbol,
                    "result_time": result_dt
                })
    except Exception as e:
        logging.error(f"[Telethon] Ошибка при получении токенов: {e}")
        raise
    return new_tokens
scheduler = BackgroundScheduler(timezone=pytz.utc)
def start_scheduler():
    logging.info("[Scheduler] Запуск планировщика...")
    if not scheduler.running:
        try:
            scheduler.start()
        except Exception as e:
            logging.error(f"[Scheduler] Failed to start scheduler: {e}")
def start_telebot():
    logging.info("[Telebot] Запуск Telegram-бота...")
    while True:
        try:
            bot.polling(none_stop=True, timeout=30, long_polling_timeout=50)
        except Exception as e:
            logging.error(f"[Telebot] Polling error: {e}. Restarting in 10 sec...")
            time.sleep(10)
def format_symbol(symbol):
    return symbol.upper() + "USDT"
async def start_telethon():
    logging.info("[Telethon] Запуск клиента...")
    client = TelegramClient(SESSION_NAME, API_ID, API_HASH)
    try:
        await client.start()
        logging.info("[Telethon] Client successfully started.")
    except Exception as e:
        logging.error(f"[Telethon] Failed to start client: {e}")
        raise
    # Setup event handler
    @client.on(events.NewMessage(chats=CHANNEL))
    async def new_message_handler(event):
        text = event.message.message
        if text is None:
            return
        match = re.search(POST_REGEX, text, re.DOTALL)
        if not match:
            return
        raw_symbol = match.group('token').strip()
        symbol = format_symbol(raw_symbol)
        result_date_str = match.group('result_date').strip()
        try:
            result_dt = datetime.strptime(result_date_str, "%d.%m.%Y %H:%M")
            result_dt = pytz.utc.localize(result_dt)
        except ValueError:
            logging.error(f"[Telethon] Невозможно разобрать дату из нового сообщения: {result_date_str}")
            return
        now = datetime.utcnow().replace(tzinfo=pytz.utc)
        if result_dt > now:
            logging.info(f"[Telethon] Новый токен из чата для планирования: {symbol} (Result: {result_dt})")
            schedule_long({"symbol": symbol, "result_time": result_dt})
    # Fetch initial tokens after start
    try:
        tokens = await fetch_new_tokens(client)
        for token in tokens:
            schedule_long(token)
        logging.info("[Telethon] Initial tokens fetched and scheduled.")
    except Exception as e:
        logging.error(f"[Telethon] Failed to fetch initial tokens: {e}")
    # Run the client with reconnect loop
    while True:
        try:
            await client.run_until_disconnected()
        except Exception as e:
            logging.error(f"[Telethon] Disconnected: {e}. Reconnecting in 10 sec...")
            await asyncio.sleep(10)
def schedule_long(token_info):
    symbol = token_info["symbol"]
    run_time = token_info["result_time"]
    now = datetime.utcnow().replace(tzinfo=pytz.utc)
    if run_time <= now:
        logging.info(f"[Scheduler] {symbol} уже в прошлом, запускаем немедленно")
        notify_all_enabled_users(symbol)
        return
    job_id = f"long_{symbol}_{run_time.strftime('%Y%m%d%H%M')}"
    if scheduler.get_job(job_id):
        logging.info(f"[Scheduler] Задача {job_id} уже запланирована, пропускаем.")
        return
    logging.info(f"[Scheduler] Планируем лонг для {symbol} на {run_time} (UTC)")
    scheduler.add_job(notify_all_enabled_users, trigger=DateTrigger(run_date=run_time), args=[symbol], id=job_id, misfire_grace_time=60)Бот:
- Подключается к Telegram через ваш аккаунт (нужен API ID и Hash). 
- Читает последние 50 сообщений из @TokenSplashBybit. 
- Находит новые анонсы. 
- Если время Result в будущем — ставит задачу в календарь. 
Новое сообщение? → Бот сразу реагирует и планирует сделку.
Планировщик (APScheduler)
Это "будильник". Если Result в 14:30 — бот ставит задачу:
"В 14:30:00 открыть лонг по LAUSDT"
В назначенное время задача сработает. Удобно и не заставляет нас постоянно проверять время в скрипте.
Торговля
def get_valid_qty(session, symbol, raw_qty):
    try:
        info = session.get_instruments_info(category="linear", symbol=symbol)
        
        if 'result' not in info or 'list' not in info['result'] or not info['result']['list']:
            logging.error(f"❌ Пара {symbol} не найдена в get_instruments_info.")
            return None
        lot_filter = info['result']['list'][0].get('lotSizeFilter', {})
        step = safe_float(lot_filter.get('qtyStep', 0))
        min_qty = safe_float(lot_filter.get('minOrderQty', 0))
        if step == 0:
            logging.error(f"❌ qtyStep равен 0 для {symbol}")
            return None
        qty = max(raw_qty, min_qty)
        valid_qty = (qty // step) * step
        logging.info(f"qty: {valid_qty}, step: {step}, min_qty: {min_qty}")
        return valid_qty
    except Exception as e:
        logging.error(f"⚠️ Ошибка получения допустимого объема для {symbol}: {e}")
        return None
def step_qty(qty, qty_step):
    if qty_step == 0:
        return qty
    return (qty // qty_step) * qty_step
def long_token(user_id, token_symbol):
    logging.info(f"▶️ Лонг {token_symbol} for user {user_id}")
    token_symbol = token_symbol + 'USDT'
    try:
        data = load_user_data(user_id)
        if not all(key in data for key in ['api_key', 'secret', 'leverage', 'margin']):
            logging.warning(f"Пользователь {user_id} не настроил все параметры. Пропуск.")
            bot.send_message(user_id, "❌ Не все параметры настроены. Настройте в ⚙️ Настройки.")
            return
        session = HTTP(api_key=data['api_key'], api_secret=data['secret'], recv_window=60000)
        res = session.get_tickers(category="linear", symbol=token_symbol)
        if not res['result']['list']:
            logging.error(f"❌ {token_symbol} пока не торгуется.")
            bot.send_message(user_id, f"❌ {token_symbol} пока не торгуется.")
            return
        info = session.get_instruments_info(category="linear", symbol=token_symbol)
        if not info['result']['list']:
            logging.error(f"❌ {token_symbol} не имеет linear futures.")
            bot.send_message(user_id, f"Фьючерс на токен {token_symbol} не существует! Ошибка сделки.")
            return
        # Безопасное получение параметров с проверкой на пустые строки
        price_filter = info['result']['list'][0]['priceFilter']
        tick_size = safe_float(price_filter['tickSize'], 0.01)  # дефолтное значение 0.01 если пусто
        price_precision = abs(Decimal(str(tick_size)).as_tuple().exponent)
        lot_filter = info['result']['list'][0]['lotSizeFilter']
        qty_step = safe_float(lot_filter.get('qtyStep', ''), 0.001)  # дефолтное значение 0.001 если пусто
        min_order_qty = safe_float(lot_filter.get('minOrderQty', ''), 0.001)
        logging.info(f"Параметры инструмента: tick_size={tick_size}, qty_step={qty_step}, min_order_qty={min_order_qty}")
        price = safe_float(res['result']['list'][0]['lastPrice'], 0)
        if price == 0:
            logging.error(f"❌ Цена для {token_symbol} равна 0")
            bot.send_message(user_id, f"❌ Не удалось получить цену для {token_symbol}")
            return
        leverage = safe_int(data.get("leverage", 5))
        margin = safe_float(data.get("margin", 10))
        raw_qty = leverage * margin / price
        
        full_qty = get_valid_qty(session, token_symbol, raw_qty)
        if full_qty is None or full_qty == 0:
            bot.send_message(user_id, f"❌ Не удалось определить объем для {token_symbol}")
            return
        # Balance check
        balance_res = session.get_wallet_balance(accountType="UNIFIED")
        if balance_res['retCode'] != 0:
            logging.error(f"Ошибка баланса: {balance_res['retMsg']}")
            bot.send_message(user_id, "❌ Ошибка проверки баланса на Bybit.")
            return
        
        # Безопасное получение баланса
        usdt_balance = 0
        try:
            coins = balance_res['result']['list'][0]['coin']
            for coin in coins:
                if coin['coin'] == 'USDT':
                    usdt_balance = safe_float(coin.get('availableToWithdraw', 0))
                    break
        except (KeyError, IndexError, TypeError) as e:
            logging.error(f"Ошибка парсинга баланса: {e}")
            bot.send_message(user_id, "❌ Ошибка получения баланса USDT.")
            return
        required_margin = full_qty * price / leverage
        # Расчет объемов с безопасным округлением
        buy_qty = step_qty(full_qty * 0.7, qty_step)
        tp1_qty = step_qty(full_qty * 0.4, qty_step)
        tp2_qty = step_qty(full_qty * 0.3, qty_step)
        sl_qty = step_qty(full_qty * 0.3, qty_step)
        # Проверка что объемы не нулевые
        if buy_qty == 0 or tp1_qty == 0 or tp2_qty == 0 or sl_qty == 0:
            logging.error(f"❌ Один из объемов равен 0: buy={buy_qty}, tp1={tp1_qty}, tp2={tp2_qty}, sl={sl_qty}")
            bot.send_message(user_id, f"❌ Рассчитанные объемы слишком малы для {token_symbol}")
            return
            
        tp1 = round(price * (1 + TP1_PCT) / tick_size) * tick_size
        tp2 = round(price * (1 + TP2_PCT) / tick_size) * tick_size
        sl = round(price * (1 - STOP_LOSS_PCT) / tick_size) * tick_size
        logging.info(f"Размещение ордеров: buy={buy_qty}, tp1={tp1_qty}@{tp1}, tp2={tp2_qty}@{tp2}, sl={sl_qty}@{sl}")
        # Buy 70% with position SL
        session.place_order(
            category="linear",
            symbol=token_symbol,
            side="Buy",
            order_type="Market",
            qty=round(buy_qty, 0),
            reduce_only=False,
            time_in_force="GoodTillCancel",
            stopLoss=str(sl)  # явное преобразование в строку
        )
        # TP1 limit sell 40%
        session.place_order(
            category="linear",
            symbol=token_symbol,
            side="Sell",
            order_type="Limit",
            qty=round(buy_qty, 0),
            price=str(tp1),  # явное преобразование в строку
            reduce_only=True,
            time_in_force="GoodTillCancel"
        )
        # TP2 limit sell 30%
        session.place_order(
            category="linear",
            symbol=token_symbol,
            side="Sell",
            order_type="Limit",
            qty=float(tp2_qty),
            price=str(tp2),  # явное преобразование в строку
            reduce_only=True,
            time_in_force="GoodTillCancel"
        )
        bot.send_message(user_id, f"✅ Лонг по {token_symbol} выполнен по цене {price:.2f}")
    except Exception as err:
        logging.error(f'Error while placing order for {token_symbol}: {err}')
        bot.send_message(user_id, f"❌ Ошибка выполнения ордера для {token_symbol}: {str(err)}")Когда время пришло, бот:
- Заходит в ваш аккаунт Bybit (через API-ключ). 
- Смотрит текущую цену. 
- 
Считает объём: raw_qty = leverage * margin / priceНапример: 10 × 20 ÷ 0.1 = 2000 токенов. 
- Корректирует под правила Bybit (шаг, минимум). 
- 
Открывает 4 ордера: - 70% — рыночный Buy. 
- 40% — Limit Sell на +3% (TP1). 
- 30% — Limit Sell на +6% (TP2). 
- 30% — Stop Market на -2% (SL). 
 
Пишет вам в бота:
Лонг по LAUSDT выполнен по цене 0.10
Запуск
def main():
    logging.info("[Main] Starting bot...")
    try:
        # Запускаем планировщик
        threading.Thread(target=start_scheduler, daemon=True).start()
        # Запускаем telebot
        threading.Thread(target=start_telebot, daemon=True).start()
        # Telethon в отдельном потоке
        def run_telethon():
            logging.info("[Telethon Thread] Starting...")
            try:
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)
                loop.run_until_complete(start_telethon())
            except Exception as e:
                logging.error(f"[Telethon Thread] Failed: {e}")
                run_telethon()
        threading.Thread(target=run_telethon, daemon=True).start()
        # Keep main thread alive
        while True:
            time.sleep(1)
    except Exception as e:
        logging.error(f"[Main] Fatal error: {e}")
        raise
if __name__ == "__main__":
    main()Бот запускает 3 потока:
- Telegram-бот (кнопки). 
- Чтение канала (Telethon). 
- Планировщик (APScheduler). 
Всё работает параллельно и не падает при ошибках — есть перезапуск.
Для запуска:
- 
Установите Python и библиотеки: pip install telebot telethon pybit apscheduler
- 
Вставьте свои: - BOT TOKEN (от @BotFather) 
- API_ID, API_HASH (my.telegram.org) 
- API-ключ Bybit 
 
- Запустите — и бот начнёт работать. 
Заключение
Этот бот — пример, как автоматизировать криптотрейдинг с использованием Telegram-API и биржевых инструментов. Он упрощает участие в token splash, ловя airdrop и потенциальный рост. Но помните: торговля рискованна, тестируйте на демо, и не используйте реальные деньги без понимания. Если улучшите код — делитесь в комментариях! Код открыт для экспериментов, но заполните свои API-ключи через телеграмм бота.
Если у вас есть вопросы — пишите. Удачных трейдов!
 
           
 
sic
Вот теперь все по делу, и название, и теги, и хабы, и в общем-то идея интересная, допустим...
Но Ваш ИИ все ещё хочет кого-то обнищать, очень настойчиво. Потому что есть фундаментальные баги.
Почему стоп-лосс на 30% позиции? Допустим цена сразу идёт вниз, он закрывает 30%, а кто закроет остальное?
reduce_only=Trueэто очень правильно, но надо и размер позиции указывать ровно тот же, что при открытииqty=round(buy_qty, 0) а не qty=float(sl_qty).Лучше поменьше таких "защит". Прилетит вам по ошибке нулевой tick size ох и насчитаете там.
Для токенов, которые на старте больше 20 баксов стоят, тоже неправильно будет работать, потому что qty надо не round делать, до целого числа, а с учётом tick size формировать. Он для этого и есть. Float такой тип данных лучше вообще не трогать в биржевой арифметике.
negrbluad Автор
интересные моменты, действительно согласен.
Дело не в ии, дело в том что код был упрощён для понимания большему количеству юзеров.
Вообще самый лучший результат получаем если ввести докупку при сливе на 2% - т.е. открыть уже новую позицию, допольнительно расчитывать к ней стоп и тейк исходя из коэффициентов.
То о чём сказали поправил в статье, благодарю за внимательность
sic
Но не все поправили, рекомендую в моментах отсчёта qty и уровней закрытия перейти от float к decimal. Надеюсь не стоит рассказывать, почему на ваших ошибках округления, зарабатывать будете не Вы :)
negrbluad Автор
Если что стоп всё-таки стоял верно, просто осталась лишняя часть)
Именно он ставится здесь на ВЕСЬ обьём позиции:
# Buy 70% with position SLsession.place_order( category="linear", symbol=token_symbol, side="Buy", order_type="Market", qty=round(buy_qty, 0), reduce_only=False, time_in_force="GoodTillCancel", stopLoss=str(sl) # явное преобразование в строку )Так что за это можно не переживать