373 lines
18 KiB
Python
373 lines
18 KiB
Python
from aiogram import Router
|
||
import logging.config
|
||
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
|
||
from app.services.Bybit.functions.Futures import safe_float, calculate_total_budget, get_bybit_client
|
||
from app.states.States import update_main_settings
|
||
from logger_helper.logger_helper import LOGGING_CONFIG
|
||
|
||
logging.config.dictConfig(LOGGING_CONFIG)
|
||
logger = logging.getLogger("main_settings")
|
||
|
||
router_main_settings = Router()
|
||
|
||
|
||
async def reg_new_user_default_main_settings(id, message):
|
||
tg_id = id
|
||
|
||
trading_mode = await rq.get_for_registration_trading_mode()
|
||
margin_type = await rq.get_for_registration_margin_type()
|
||
|
||
await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type)
|
||
|
||
|
||
async def main_settings_message(id, message):
|
||
try:
|
||
data = await rq.get_user_main_settings(id)
|
||
tg_id = id
|
||
|
||
data_main_stgs = await rq.get_user_main_settings(id)
|
||
data_risk_stgs = await rq.get_user_risk_management_settings(id)
|
||
client = await get_bybit_client(tg_id)
|
||
symbol = await rq.get_symbol(tg_id)
|
||
max_martingale_steps = (data_main_stgs or {}).get('maximal_quantity', 0)
|
||
commission_fee = (data_risk_stgs or {}).get('commission_fee')
|
||
starting_quantity = safe_float((data_main_stgs or {}).get('starting_quantity'))
|
||
martingale_factor = safe_float((data_main_stgs or {}).get('martingale_factor'))
|
||
fee_info = client.get_fee_rates(category='linear', symbol=symbol)
|
||
leverage = safe_float((data_main_stgs or {}).get('size_leverage'))
|
||
price = await get_price(tg_id, symbol=symbol)
|
||
entry_price = safe_float(price)
|
||
|
||
if commission_fee == "Да":
|
||
commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate'])
|
||
else:
|
||
commission_fee_percent = 0.0
|
||
|
||
total_budget = await calculate_total_budget(
|
||
starting_quantity=starting_quantity,
|
||
martingale_factor=martingale_factor,
|
||
max_steps=max_martingale_steps,
|
||
commission_fee_percent=commission_fee_percent,
|
||
leverage=leverage,
|
||
current_price=entry_price,
|
||
)
|
||
|
||
await message.answer(f"""<b>Основные настройки</b>
|
||
|
||
<b>- Режим торговли:</b> {data['trading_mode']}
|
||
<b>- Состояние свитча:</b> {data['switch_state']}
|
||
<b>- Направление последней сделки:</b> {data['last_side']}
|
||
<b>- Тип маржи:</b> {data['margin_type']}
|
||
<b>- Размер кредитного плеча:</b> х{data['size_leverage']}
|
||
<b>- Начальная ставка:</b> {data['starting_quantity']}
|
||
<b>- Коэффициент мартингейла:</b> {data['martingale_factor']}
|
||
<b>- Текущий шаг:</b> {data['martingale_step']}
|
||
<b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}
|
||
|
||
<b>- Требуемый бюджет:</b> {total_budget:.2f} USDT
|
||
""", parse_mode='html', reply_markup=inline_markup.main_settings_markup)
|
||
except PermissionError as e:
|
||
logger.error("Authenticated endpoints require keys: %s", e)
|
||
await message.answer("Вы не авторизованы.", reply_markup=inline_markup.connect_bybit_api_message)
|
||
|
||
|
||
async def trading_mode_message(message, state):
|
||
await state.set_state(update_main_settings.trading_mode)
|
||
|
||
await message.edit_text("""<b>Режим торговли</b>
|
||
|
||
<b>Лонг</b> — стратегия, ориентированная на покупку актива с целью заработать на повышении его стоимости.
|
||
|
||
<b>Шорт</b> — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены.
|
||
|
||
<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
|
||
|
||
<em>Выберите ниже для изменений:</em>
|
||
""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup)
|
||
|
||
|
||
@router_main_settings.callback_query(update_main_settings.trading_mode)
|
||
async def state_trading_mode(callback: CallbackQuery, state):
|
||
await callback.answer()
|
||
|
||
id = callback.from_user.id
|
||
data_settings = await rq.get_user_main_settings(id)
|
||
|
||
try:
|
||
match callback.data:
|
||
case 'trade_mode_long':
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long")
|
||
await rq.update_trade_mode_user(id, 'Long')
|
||
await main_settings_message(id, callback.message)
|
||
|
||
await state.clear()
|
||
case 'trade_mode_short':
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short")
|
||
await rq.update_trade_mode_user(id, 'Short')
|
||
await main_settings_message(id, callback.message)
|
||
|
||
await state.clear()
|
||
|
||
case 'trade_mode_switch':
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch")
|
||
await rq.update_trade_mode_user(id, 'Switch')
|
||
await main_settings_message(id, callback.message)
|
||
|
||
await state.clear()
|
||
|
||
case 'trade_mode_smart':
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart")
|
||
await rq.update_trade_mode_user(id, 'Smart')
|
||
await main_settings_message(id, callback.message)
|
||
|
||
await state.clear()
|
||
except Exception as e:
|
||
logger.error("Ошибка при обновлении режима торговли: %s", e)
|
||
|
||
|
||
async def switch_mode_enabled_message(message, state):
|
||
await state.set_state(update_main_settings.switch_mode_enabled)
|
||
|
||
await message.edit_text(
|
||
f"""<b> Состояние свитча</b>
|
||
|
||
<b>По направлению</b> - по направлению последней сделки предыдущей серии
|
||
<b>Против направления</b> - против направления последней сделки предыдущей серии
|
||
|
||
<em>По умолчанию при первом запуске бота, направление сделки установлено на "Buy".</em>
|
||
<em>Выберите ниже для изменений:</em>""", parse_mode='html',
|
||
reply_markup=inline_markup.switch_state_markup)
|
||
|
||
|
||
@router_main_settings.callback_query(lambda c: c.data in ["clb_long_switch", "clb_short_switch"])
|
||
async def state_switch_mode_enabled(callback: CallbackQuery, state):
|
||
await callback.answer()
|
||
tg_id = callback.from_user.id
|
||
val = "По направлению" if callback.data == "clb_long_switch" else "Против направления"
|
||
if val == "По направлению":
|
||
await rq.update_switch_state(tg_id, "По направлению")
|
||
await callback.message.answer(f"Состояние свитча: {val}")
|
||
await main_settings_message(tg_id, callback.message)
|
||
else:
|
||
await rq.update_switch_state(tg_id, "Против направления")
|
||
await callback.message.answer(f"Состояние свитча: {val}")
|
||
await main_settings_message(tg_id, callback.message)
|
||
await state.clear()
|
||
|
||
|
||
async def size_leverage_message(message, state):
|
||
await state.set_state(update_main_settings.size_leverage)
|
||
|
||
await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html',
|
||
reply_markup=inline_markup.back_btn_list_settings_markup)
|
||
|
||
|
||
@router_main_settings.message(update_main_settings.size_leverage)
|
||
async def state_size_leverage(message: Message, state):
|
||
try:
|
||
leverage = float(message.text)
|
||
if leverage <= 0:
|
||
raise ValueError("Неверное значение")
|
||
except ValueError:
|
||
await message.answer(
|
||
"Ошибка: пожалуйста, введите положительное число для кредитного плеча."
|
||
"\nПопробуйте снова."
|
||
)
|
||
return
|
||
|
||
await state.update_data(size_leverage=message.text)
|
||
|
||
data = await state.get_data()
|
||
tg_id = message.from_user.id
|
||
symbol = await rq.get_symbol(tg_id)
|
||
leverage = data['size_leverage']
|
||
client = await get_bybit_client(tg_id)
|
||
|
||
instruments_resp = client.get_instruments_info(category="linear", symbol=symbol)
|
||
info = instruments_resp.get("result", {}).get("list", [])
|
||
|
||
max_leverage = safe_float(info[0].get("leverageFilter", {}).get("maxLeverage", 0))
|
||
|
||
if safe_float(leverage) > max_leverage:
|
||
await message.answer(
|
||
f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. "
|
||
f"Устанавливаю максимальное.",
|
||
reply_markup=inline_markup.back_to_main,
|
||
)
|
||
logger.info(
|
||
f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.")
|
||
|
||
await rq.update_size_leverange(message.from_user.id, max_leverage)
|
||
await main_settings_message(message.from_user.id, message)
|
||
await state.clear()
|
||
else:
|
||
await message.answer(f"✅ Изменено: {leverage}")
|
||
await rq.update_size_leverange(message.from_user.id, safe_float(leverage))
|
||
await main_settings_message(message.from_user.id, message)
|
||
await state.clear()
|
||
|
||
|
||
async def martingale_factor_message(message, state):
|
||
await state.set_state(update_main_settings.martingale_factor)
|
||
|
||
await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html',
|
||
reply_markup=inline_markup.back_btn_list_settings_markup)
|
||
|
||
|
||
@router_main_settings.message(update_main_settings.martingale_factor)
|
||
async def state_martingale_factor(message: Message, state):
|
||
await state.update_data(martingale_factor=message.text)
|
||
|
||
data = await state.get_data()
|
||
data_settings = await rq.get_user_main_settings(message.from_user.id)
|
||
|
||
if data['martingale_factor'].isdigit() and int(data['martingale_factor']) <= 100:
|
||
await message.answer(f"✅ Изменено: {data_settings['martingale_factor']} → {data['martingale_factor']}")
|
||
|
||
await rq.update_martingale_factor(message.from_user.id, data['martingale_factor'])
|
||
await main_settings_message(message.from_user.id, message)
|
||
|
||
await state.clear()
|
||
else:
|
||
val = data['martingale_factor']
|
||
await message.answer(
|
||
f"⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы")
|
||
|
||
await main_settings_message(message.from_user.id, message)
|
||
|
||
|
||
async def margin_type_message(message, state):
|
||
await state.set_state(update_main_settings.margin_type)
|
||
|
||
await message.edit_text("""<b>Тип маржи</b>
|
||
|
||
<b>Изолированная маржа</b>
|
||
Этот тип маржи позволяет ограничить риск конкретной позиции.
|
||
При использовании изолированной маржи вы выделяете определённую сумму средств только для одной позиции.
|
||
Если позиция начинает приносить убытки, ваши потери ограничиваются этой суммой,
|
||
и остальные средства на счёте не затрагиваются.
|
||
|
||
<b>Кросс-маржа</b>
|
||
Кросс-маржа объединяет весь маржинальный баланс на счёте и использует все доступные средства для поддержания открытых позиций.
|
||
В случае убытков средства с других позиций или баланса автоматически покрывают дефицит,
|
||
снижая риск ликвидации, но увеличивая общий риск потери капитала.
|
||
|
||
<em>Выберите ниже для изменений:</em>
|
||
""", parse_mode='html', reply_markup=inline_markup.margin_type_markup)
|
||
|
||
|
||
@router_main_settings.callback_query(update_main_settings.margin_type)
|
||
async def state_margin_type(callback: CallbackQuery, state):
|
||
callback_data = callback.data
|
||
if callback_data in ['margin_type_isolated', 'margin_type_cross']:
|
||
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)
|
||
data_settings = await rq.get_user_main_settings(tg_id)
|
||
symbol = await rq.get_symbol(tg_id)
|
||
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||
try:
|
||
active_positions = client.get_positions(category='linear', settleCoin="USDT")
|
||
|
||
positions = active_positions.get('result', {}).get('list', [])
|
||
except Exception as e:
|
||
logger.error("Ошибка при получении активных позиций: %s", e)
|
||
positions = []
|
||
|
||
for pos in positions:
|
||
size = pos.get('size')
|
||
if float(size) > 0:
|
||
await callback.answer(
|
||
"⚠️ Маржинальный режим нельзя менять при открытой позиции"
|
||
)
|
||
return
|
||
|
||
try:
|
||
match callback.data:
|
||
case 'margin_type_isolated':
|
||
await callback.answer()
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated")
|
||
|
||
await rq.update_margin_type(tg_id, 'Isolated')
|
||
await main_settings_message(tg_id, callback.message)
|
||
|
||
await state.clear()
|
||
case 'margin_type_cross':
|
||
await callback.answer()
|
||
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross")
|
||
|
||
await rq.update_margin_type(tg_id, 'Cross')
|
||
await main_settings_message(tg_id, callback.message)
|
||
|
||
await state.clear()
|
||
except Exception as e:
|
||
logger.error("Ошибка при изменении типа маржи: %s", e)
|
||
else:
|
||
await callback.answer()
|
||
await main_settings_message(callback.from_user.id, callback.message)
|
||
|
||
await state.clear()
|
||
|
||
|
||
async def starting_quantity_message(message, state):
|
||
await state.set_state(update_main_settings.starting_quantity)
|
||
|
||
await message.edit_text("Введите <b>начальную ставку:</b>", parse_mode='html',
|
||
reply_markup=inline_markup.back_btn_list_settings_markup)
|
||
|
||
|
||
@router_main_settings.message(update_main_settings.starting_quantity)
|
||
async def state_starting_quantity(message: Message, state):
|
||
await state.update_data(starting_quantity=message.text)
|
||
|
||
data = await state.get_data()
|
||
data_settings = await rq.get_user_main_settings(message.from_user.id)
|
||
|
||
if data['starting_quantity'].isdigit():
|
||
await message.answer(f"✅ Изменено: {data_settings['starting_quantity']} → {data['starting_quantity']}")
|
||
|
||
await rq.update_starting_quantity(message.from_user.id, data['starting_quantity'])
|
||
await main_settings_message(message.from_user.id, message)
|
||
|
||
await state.clear()
|
||
else:
|
||
await message.answer("⛔️ Ошибка: вы вводите неверные символы")
|
||
|
||
await main_settings_message(message.from_user.id, message)
|
||
|
||
|
||
async def maximum_quantity_message(message, state):
|
||
await state.set_state(update_main_settings.maximal_quantity)
|
||
|
||
await message.edit_text("Введите максимальное количество серии ставок:",
|
||
reply_markup=inline_markup.back_btn_list_settings_markup)
|
||
|
||
|
||
@router_main_settings.message(update_main_settings.maximal_quantity)
|
||
async def state_maximal_quantity(message: Message, state):
|
||
await state.update_data(maximal_quantity=message.text)
|
||
|
||
data = await state.get_data()
|
||
data_settings = await rq.get_user_main_settings(message.from_user.id)
|
||
|
||
if data['maximal_quantity'].isdigit() and int(data['maximal_quantity']) <= 100:
|
||
await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']} → {data['maximal_quantity']}")
|
||
|
||
await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity'])
|
||
await main_settings_message(message.from_user.id, message)
|
||
|
||
await state.clear()
|
||
else:
|
||
val = data['maximal_quantity']
|
||
await message.answer(
|
||
f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы')
|
||
logger.error(f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы')
|
||
|
||
await main_settings_message(message.from_user.id, message)
|