Привет, 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) # явное преобразование в строку )Так что за это можно не переживать