Files
stcs/app/services/Bybit/functions/functions.py
2025-08-25 17:07:41 +05:00

410 lines
17 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import logging.config
from aiogram import F, Router
from logger_helper.logger_helper import LOGGING_CONFIG
from app.services.Bybit.functions.Futures import close_user_trade, get_active_positions, close_trade_after_delay, \
active_close_tasks, active_start_tasks, trading_cycle, open_position
from app.services.Bybit.functions.balance import get_balance
import app.telegram.Keyboards.inline_keyboards as inline_markup
from pybit.unified_trading import HTTP
import app.telegram.database.requests as rq
from aiogram.types import Message, CallbackQuery
from app.services.Bybit.functions.price_symbol import get_price
# FSM - Механизм состояния
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
from app.services.Bybit.functions.get_valid_symbol import get_valid_symbols
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("functions")
router_functions_bybit_trade = Router()
class state_update_symbol(StatesGroup):
symbol = State()
class state_update_entry_type(StatesGroup):
entry_type = State()
class TradeSetup(StatesGroup):
waiting_for_timer = State()
waiting_for_positive_percent = State()
class state_limit_price(StatesGroup):
price = State()
class CloseTradeTimerState(StatesGroup):
waiting_for_delay = State()
@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main']))
async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMContext):
balance = await get_balance(callback.from_user.id, callback.message)
price = await get_price(callback.from_user.id)
if balance:
symbol = await rq.get_symbol(callback.from_user.id)
text = f'''💎 Торговля на Bybit
⚖️ Ваш баланс (USDT): {balance}
📊 Текущая торговая пара: {symbol}
$$$ Цена: {price}
Как начать торговлю?
1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.
2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).
3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.
'''
await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
async def start_bybit_trade_message(message: Message, state: FSMContext):
balance = await get_balance(message.from_user.id, message)
price = await get_price(message.from_user.id)
if balance:
symbol = await rq.get_symbol(message.from_user.id)
text = f'''💎 Торговля на Bybit
⚖️ Ваш баланс (USDT): {balance}
📊 Текущая торговая пара: {symbol}
$$$ Цена: {price}
Как начать торговлю?
1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.
2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).
3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.
'''
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
@router_functions_bybit_trade.callback_query(F.data == 'clb_update_trading_pair')
async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMContext):
await state.set_state(state_update_symbol.symbol)
await callback.message.answer(
text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ',
reply_markup=inline_markup.cancel)
@router_functions_bybit_trade.message(state_update_symbol.symbol)
async def update_symbol_for_trade(message: Message, state: FSMContext):
user_input = message.text.strip().upper()
exists = await get_valid_symbols(message.from_user.id, user_input)
if not exists:
await message.answer("Введена некорректная торговая пара или такой пары нет в списке. Попробуйте снова.")
return
await state.update_data(symbol=message.text)
await message.answer('Пара была успешно обновлена')
await rq.update_symbol(message.from_user.id, user_input)
await start_bybit_trade_message(message, state)
await state.clear()
@router_functions_bybit_trade.callback_query(F.data == 'clb_update_entry_type')
async def update_entry_type_message(callback: CallbackQuery, state: FSMContext):
await state.set_state(state_update_entry_type.entry_type)
await callback.message.answer("Выберите тип входа в позицию:", reply_markup=inline_markup.entry_order_type_markup)
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:'))
async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext):
order_type = callback.data.split(':')[1]
if order_type not in ['Market', 'Limit']:
await callback.answer("Ошибка выбора", show_alert=True)
return
if order_type == 'Limit':
await state.set_state(state_limit_price.price)
await callback.message.answer("Введите цену лимитного ордера:", reply_markup=inline_markup.cancel)
await callback.answer()
return
try:
await state.update_data(entry_order_type=order_type)
await rq.update_entry_order_type(callback.from_user.id, order_type)
await callback.message.answer(f"Выбран тип входа в позицию: {order_type}",
reply_markup=inline_markup.start_trading_markup)
await callback.answer()
except Exception as e:
logger.error(f"Произошла ошибка при обновлении типа входа в позицию: {e}")
await callback.message.answer("Произошла ошибка при обновлении типа входа в позицию",
reply_markup=inline_markup.back_to_main)
await state.clear()
@router_functions_bybit_trade.message(state_limit_price.price)
async def set_limit_price(message: Message, state: FSMContext):
try:
price = float(message.text)
if price <= 0:
await message.answer("Цена должна быть положительным числом. Попробуйте снова.",
reply_markup=inline_markup.cancel)
return
except ValueError:
await message.answer("Некорректный формат цены. Введите число.", reply_markup=inline_markup.cancel)
return
await state.update_data(entry_order_type='Limit', limit_price=price)
await rq.update_entry_order_type(message.from_user.id, 'Limit')
await rq.update_limit_price(message.from_user.id, price)
await message.answer(f"Цена лимитного ордера установлена: {price}", reply_markup=inline_markup.start_trading_markup)
await state.clear()
@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals")
async def show_my_trades_callback(callback: CallbackQuery):
await callback.answer() # сразу отвечаем Telegram, освобождаем callback
async def process():
tg_id = callback.from_user.id
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
trades = await get_active_positions(callback.message, api_key, secret_key)
if not trades:
await callback.message.answer("Нет активных позиций.")
return
keyboard = inline_markup.create_trades_inline_keyboard(trades)
await callback.message.answer(
"Выберите сделку из списка:",
reply_markup=keyboard
)
asyncio.create_task(process())
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('select_trade:'))
async def on_trade_selected(callback: CallbackQuery):
await callback.answer()
async def process():
tg_id = callback.from_user.id
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
positions = await get_active_positions(callback.message, api_key, secret_key)
if not positions:
await callback.message.answer("Позиция не найдена")
return
pos = positions[0]
text = (f"Информация по позиции:\n"
f"Название: {pos.get('symbol')}\n"
f"Направление: {pos.get('side')}\n"
f"Цена покупки: {pos.get('entryPrice')}\n"
f"Текущая цена: {pos.get('price', 'N/A')}")
keyboard = inline_markup.create_close_deal_markup(pos.get('symbol'))
await callback.message.answer(text, reply_markup=keyboard)
asyncio.create_task(process())
@router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading")
async def start_trading_process(callback: CallbackQuery):
tg_id = callback.from_user.id
message = callback.message
# Получаем настройки пользователя
data_main_stgs = await rq.get_user_main_settings(tg_id)
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
symbol = await rq.get_symbol(tg_id)
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
trading_mode = data_main_stgs.get('trading_mode')
# Проверка API ключей
if not api_key or not secret_key:
await message.answer("❗️ У вас не настроены API ключи для Bybit.")
await callback.answer()
return
# Проверка режима торговли
if trading_mode not in ['Long', 'Short', 'Smart', 'Switch']:
await message.answer(f"❗️ Некорректный торговый режим: {trading_mode}")
await callback.answer()
return
# Проверка допустимости маржинального режима
if margin_mode not in ['Isolated', 'Cross']:
margin_mode = 'Isolated'
# Проверяем открытые позиции и маржинальный режим
client = HTTP(api_key=api_key, api_secret=secret_key)
try:
positions_resp = client.get_positions(category='linear', symbol=symbol)
positions = positions_resp.get('result', {}).get('list', [])
except Exception as e:
logger.error(f"Ошибка при получении позиций: {e}")
positions = []
for pos in positions:
size = pos.get('size')
existing_margin_mode = pos.get('margin_mode')
if size and float(size) > 0 and existing_margin_mode and existing_margin_mode != margin_mode:
await callback.answer(
f"⚠️ Маржинальный режим нельзя менять при открытой позиции "
f"(текущий режим: {existing_margin_mode})", show_alert=True)
return
# Определяем сторону для открытия позиции
if trading_mode == 'Long':
side = 'Buy'
elif trading_mode == 'Short':
side = 'Sell'
else:
await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.")
await callback.answer()
return
# Сообщаем о начале торговли
await message.answer("Начинаю торговлю с использованием текущих настроек...")
timer_data = await rq.get_user_timer(tg_id)
if isinstance(timer_data, dict):
timer_minute = timer_data.get('timer_minutes', 0)
else:
timer_minute = timer_data or 0
logger.info(f"Timer minutes for user {tg_id}: {timer_minute}")
symbol = await rq.get_symbol(tg_id)
if timer_minute > 0:
old_task = active_start_tasks.get(tg_id)
if old_task:
old_task.cancel()
# можно ждать завершения старой задачи, если в async функции
task = asyncio.create_task(trading_cycle(tg_id, message, symbol))
active_start_tasks[tg_id] = task
await message.answer(f"Торговля начнётся через {timer_minute} мин. Для отмены нажмите кнопку ниже.",
reply_markup=inline_markup.cancel_start_markup)
await rq.update_user_timer(tg_id, minutes=0)
else:
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
async def cancel_start_callback(callback: CallbackQuery):
tg_id = callback.from_user.id
task = active_start_tasks.get(tg_id)
if task:
task.cancel()
del active_start_tasks[tg_id]
await callback.message.answer("Торговля по таймеру отменена.", reply_markup=inline_markup.back_to_main)
else:
await callback.message.answer("Нет активности для отмены.", reply_markup=inline_markup.back_to_main)
await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
async def cancel_start_callback(callback: CallbackQuery):
tg_id = callback.from_user.id
task = active_close_tasks.get(tg_id)
if task:
task.cancel()
del active_close_tasks[tg_id]
await callback.message.answer("Таймер отменен.", reply_markup=inline_markup.back_to_main)
else:
await callback.message.answer("Нет активности для отмены.", reply_markup=inline_markup.back_to_main)
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
async def close_trade_callback(callback: CallbackQuery):
symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id
result = await close_user_trade(tg_id, symbol)
if result:
await callback.message.answer(f"Сделка {symbol} успешно закрыта.", reply_markup=inline_markup.back_to_main)
else:
await callback.message.answer(f"Не удалось закрыть сделку {symbol}.")
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:"))
async def ask_close_delay(callback: CallbackQuery, state: FSMContext):
symbol = callback.data.split(":")[1]
await state.update_data(symbol=symbol)
await state.set_state(CloseTradeTimerState.waiting_for_delay)
await callback.message.answer("Введите задержку в минутах до закрытия сделки (например, 60):")
await callback.answer()
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
async def process_close_delay(message: Message, state: FSMContext):
try:
delay_minutes = int(message.text.strip())
if delay_minutes <= 0:
await message.answer("Введите положительное число.")
return
except ValueError:
await message.answer("Некорректный ввод. Введите число в минутах.")
return
data = await state.get_data()
symbol = data.get("symbol")
tg_id = message.from_user.id
delay = delay_minutes * 60
# Отменяем предыдущую задачу, если есть
if tg_id in active_close_tasks:
active_close_tasks[tg_id].cancel()
task = asyncio.create_task(close_trade_after_delay(tg_id, message, symbol, delay))
active_close_tasks[tg_id] = task
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay} секунд.",
reply_markup=inline_markup.cancel_start_markup)
await state.clear()
@router_functions_bybit_trade.callback_query(F.data == "clb_change_martingale_reset")
async def reset_martingale(callback: CallbackQuery):
await callback.answer()
tg_id = callback.from_user.id
await rq.update_martingale_step(tg_id, 0)
await callback.message.answer("Сброс шагов мартингейла выполнен. Торговля начнется заново с начального объема.",
reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel")
async def cancel(callback: CallbackQuery, state: FSMContext):
await state.clear()
await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main)
await callback.answer()