devel #11
@@ -7,8 +7,8 @@ from aiogram.types import CallbackQuery
|
||||
import app.telegram.keyboards.inline as kbi
|
||||
import database.request as rq
|
||||
from app.bybit import get_bybit_client
|
||||
from app.bybit.get_functions.get_tickers import get_tickers
|
||||
from app.helper_functions import calculate_total_budget, get_base_currency, safe_float
|
||||
|
||||
from app.helper_functions import calculate_total_budget, safe_float
|
||||
from logger_helper.logger_helper import LOGGING_CONFIG
|
||||
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
@@ -40,77 +40,30 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
|
||||
return
|
||||
|
||||
trade_mode_map = {
|
||||
"Merged_Single": "Односторонний режим",
|
||||
"Both_Sides": "Хеджирование",
|
||||
"Long": "Лонг",
|
||||
"Short": "Шорт",
|
||||
"Switch": "Свитч",
|
||||
}
|
||||
margin_type_map = {
|
||||
"ISOLATED_MARGIN": "Изолированная",
|
||||
"REGULAR_MARGIN": "Кросс",
|
||||
}
|
||||
order_type_map = {"Market": "Рыночный", "Limit": "Лимитный"}
|
||||
|
||||
trade_mode = additional_data.trade_mode or ""
|
||||
margin_type = additional_data.margin_type or ""
|
||||
order_type = additional_data.order_type or ""
|
||||
|
||||
trade_mode_rus = trade_mode_map.get(trade_mode, trade_mode)
|
||||
margin_type_rus = margin_type_map.get(margin_type, margin_type)
|
||||
order_type_rus = order_type_map.get(order_type, "Условный")
|
||||
switch_side = additional_data.switch_side
|
||||
|
||||
def f(x):
|
||||
return safe_float(x)
|
||||
|
||||
leverage = f(additional_data.leverage)
|
||||
leverage_to_buy = f(additional_data.leverage_to_buy)
|
||||
leverage_to_sell = f(additional_data.leverage_to_sell)
|
||||
martingale = f(additional_data.martingale_factor)
|
||||
max_bets = additional_data.max_bets_in_series
|
||||
quantity = f(additional_data.order_quantity)
|
||||
limit_price = f(additional_data.limit_price)
|
||||
trigger_price = f(additional_data.trigger_price) or 0
|
||||
|
||||
tickers = await get_tickers(tg_id=tg_id, symbol=symbol)
|
||||
price_symbol = safe_float(tickers.get("lastPrice")) or 0
|
||||
bid = f(tickers.get("bid1Price")) or 0
|
||||
ask = f(tickers.get("ask1Price")) or 0
|
||||
|
||||
sym = get_base_currency(symbol)
|
||||
|
||||
if trade_mode == "Merged_Single":
|
||||
leverage_str = f"{leverage:.2f}x"
|
||||
else:
|
||||
if margin_type == "ISOLATED_MARGIN":
|
||||
leverage_str = f"{leverage_to_buy:.2f}x:{leverage_to_sell:.2f}x"
|
||||
else:
|
||||
leverage_str = f"{leverage:.2f}x"
|
||||
|
||||
conditional_order_type = additional_data.conditional_order_type or ""
|
||||
conditional_order_type_rus = (
|
||||
"Лимитный"
|
||||
if conditional_order_type == "Limit"
|
||||
else (
|
||||
"Рыночный"
|
||||
if conditional_order_type == "Market"
|
||||
else conditional_order_type
|
||||
)
|
||||
)
|
||||
|
||||
conditional_order_type_text = (
|
||||
f"- Тип условного ордера: {conditional_order_type_rus}\n"
|
||||
if order_type == "Conditional"
|
||||
else ""
|
||||
)
|
||||
|
||||
limit_price_text = ""
|
||||
trigger_price_text = ""
|
||||
|
||||
if order_type == "Limit":
|
||||
limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n"
|
||||
elif order_type == "Conditional":
|
||||
if conditional_order_type == "Limit":
|
||||
limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n"
|
||||
trigger_price_text = f"- Триггер цена: {trigger_price:.4f} USDT\n"
|
||||
|
||||
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
|
||||
commission_fee = risk_management_data.commission_fee
|
||||
client = await get_bybit_client(tg_id=tg_id)
|
||||
@@ -124,54 +77,32 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
|
||||
else:
|
||||
commission_fee_percent = 0.0
|
||||
|
||||
if order_type == "Conditional":
|
||||
if conditional_order_type == "Limit":
|
||||
entry_price = limit_price
|
||||
ask_price = limit_price
|
||||
bid_price = limit_price
|
||||
else:
|
||||
ask_price = trigger_price
|
||||
bid_price = trigger_price
|
||||
entry_price = trigger_price
|
||||
else:
|
||||
if order_type == "Limit":
|
||||
entry_price = limit_price
|
||||
ask_price = limit_price
|
||||
bid_price = limit_price
|
||||
else:
|
||||
entry_price = price_symbol
|
||||
ask_price = ask
|
||||
bid_price = bid
|
||||
switch_side_mode = ""
|
||||
if trade_mode == "Switch":
|
||||
switch_side_mode = f"- Направление первой сделки: {switch_side}\n"
|
||||
|
||||
durability_buy = quantity * bid_price
|
||||
durability_sell = quantity * ask_price
|
||||
quantity_price = quantity * entry_price
|
||||
quantity_price = quantity * trigger_price
|
||||
total_commission = quantity_price * commission_fee_percent
|
||||
total_budget = await calculate_total_budget(
|
||||
quantity=durability_buy,
|
||||
quantity=quantity,
|
||||
martingale_factor=martingale,
|
||||
max_steps=max_bets,
|
||||
commission_fee_percent=total_commission,
|
||||
)
|
||||
text = (
|
||||
f"Основные настройки:\n\n"
|
||||
f"- Режим позиции: {trade_mode_rus}\n"
|
||||
f"- Режим торговли: {trade_mode_rus}\n"
|
||||
f"{switch_side_mode}"
|
||||
f"- Тип маржи: {margin_type_rus}\n"
|
||||
f"- Размер кредитного плеча: {leverage_str}\n"
|
||||
f"- Тип ордера: {order_type_rus}\n"
|
||||
f"- Количество ордера: {quantity} {sym}\n"
|
||||
f"- Размер кредитного плеча: {leverage:.2f}\n"
|
||||
f"- Базовая ставка: {quantity} USDT\n"
|
||||
f"- Коэффициент мартингейла: {martingale:.2f}\n"
|
||||
f"{conditional_order_type_text}"
|
||||
f"{trigger_price_text}"
|
||||
f"{limit_price_text}"
|
||||
f"- Триггер цена: {trigger_price:.4f} USDT\n"
|
||||
f"- Максимальное кол-во ставок в серии: {max_bets}\n\n"
|
||||
f"- Стоимость: {durability_buy:.2f}/{durability_sell:.2f} USDT\n"
|
||||
f"- Рекомендуемый бюджет: {total_budget:.4f} USDT\n"
|
||||
f"- Бюджет серии: {total_budget:.4f} USDT\n"
|
||||
)
|
||||
|
||||
keyboard = kbi.get_additional_settings_keyboard(
|
||||
current_order_type=order_type, conditional_order=conditional_order_type
|
||||
)
|
||||
keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode)
|
||||
await callback_query.message.edit_text(text=text, reply_markup=keyboard)
|
||||
logger.debug(
|
||||
"Command additional_settings processed successfully for user: %s", tg_id
|
||||
@@ -202,7 +133,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N
|
||||
if risk_management_data:
|
||||
take_profit_percent = risk_management_data.take_profit_percent or ""
|
||||
stop_loss_percent = risk_management_data.stop_loss_percent or ""
|
||||
max_risk_percent = risk_management_data.max_risk_percent or ""
|
||||
commission_fee = risk_management_data.commission_fee or ""
|
||||
commission_fee_rus = (
|
||||
"Да" if commission_fee == "Yes_commission_fee" else "Нет"
|
||||
@@ -212,7 +142,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N
|
||||
text=f"Риск-менеджмент:\n\n"
|
||||
f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n"
|
||||
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n"
|
||||
f"- Максимальный риск на сделку (в % от баланса): {max_risk_percent}%\n\n"
|
||||
f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n",
|
||||
reply_markup=kbi.risk_management,
|
||||
)
|
||||
|
@@ -12,9 +12,7 @@ from app.bybit.open_positions import start_trading_cycle
|
||||
from app.helper_functions import safe_float
|
||||
from app.telegram.tasks.tasks import (
|
||||
add_start_task_merged,
|
||||
add_start_task_switch,
|
||||
cancel_start_task_merged,
|
||||
cancel_start_task_switch,
|
||||
cancel_start_task_merged
|
||||
)
|
||||
from logger_helper.logger_helper import LOGGING_CONFIG
|
||||
|
||||
@@ -35,97 +33,20 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
|
||||
"""
|
||||
try:
|
||||
await state.clear()
|
||||
additional_data = await rq.get_user_additional_settings(
|
||||
tg_id=callback_query.from_user.id
|
||||
)
|
||||
trade_mode = additional_data.trade_mode
|
||||
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
|
||||
deals = await get_active_positions_by_symbol(
|
||||
tg_id=callback_query.from_user.id, symbol=symbol
|
||||
)
|
||||
position = next((d for d in deals if d.get("symbol") == symbol), None)
|
||||
|
||||
if position:
|
||||
size = position.get("size", 0)
|
||||
position_idx = position.get("positionIdx")
|
||||
else:
|
||||
size = 0
|
||||
position_idx = None
|
||||
|
||||
if position_idx != 0 and safe_float(size) > 0 and trade_mode == "Merged_Single":
|
||||
if safe_float(size) > 0:
|
||||
await callback_query.answer(
|
||||
text="У вас есть активная позиция в режиме хеджирования. "
|
||||
"Открытие сделки в одностороннем режиме невозможно.",
|
||||
)
|
||||
return
|
||||
|
||||
if position_idx == 0 and safe_float(size) > 0 and trade_mode == "Both_Sides":
|
||||
await callback_query.answer(
|
||||
text="У вас есть активная позиция в одностороннем режиме. "
|
||||
"Открытие сделки в режиме хеджирования невозможно.",
|
||||
)
|
||||
return
|
||||
|
||||
if trade_mode == "Merged_Single":
|
||||
await callback_query.message.edit_text(
|
||||
text="Выберите режим торговли:\n\n"
|
||||
"Лонг - все сделки серии открываются на покупку.\n"
|
||||
"Шорт - все сделки серии открываются на продажу.\n"
|
||||
"Свитч - направление каждой сделки серии меняется по переменно.\n",
|
||||
reply_markup=kbi.merged_start_trading,
|
||||
)
|
||||
else: # trade_mode == "Both_Sides":
|
||||
await callback_query.message.edit_text(
|
||||
text="Выберите режим торговли:\n\n"
|
||||
"Лонг - все сделки открываются на покупку.\n"
|
||||
"Шорт - все сделки открываются на продажу.\n",
|
||||
reply_markup=kbi.both_start_trading,
|
||||
)
|
||||
logger.debug(
|
||||
"Command start_trading processed successfully for user: %s",
|
||||
callback_query.from_user.id,
|
||||
)
|
||||
except Exception as e:
|
||||
await callback_query.answer(text="Произошла ошибка при запуске торговли")
|
||||
logger.error(
|
||||
"Error processing command start_trading for user %s: %s",
|
||||
callback_query.from_user.id,
|
||||
e,
|
||||
)
|
||||
|
||||
|
||||
@router_start_trading.callback_query(lambda c: c.data == "long" or c.data == "short")
|
||||
async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -> None:
|
||||
"""
|
||||
Handles the "long" or "short" callback query.
|
||||
Clears the FSM state and starts the trading cycle.
|
||||
:param callback_query: Message
|
||||
:param state: FSMContext
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
if callback_query.data == "long":
|
||||
side = "Buy"
|
||||
elif callback_query.data == "short":
|
||||
side = "Sell"
|
||||
else:
|
||||
await callback_query.answer(text="Произошла ошибка при запуске торговли")
|
||||
return
|
||||
|
||||
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
|
||||
deals = await get_active_positions_by_symbol(
|
||||
tg_id=callback_query.from_user.id, symbol=symbol
|
||||
)
|
||||
position = next((d for d in deals if d.get("symbol") == symbol), None)
|
||||
if position:
|
||||
size = position.get("size", 0)
|
||||
position_idx = position.get("positionIdx")
|
||||
else:
|
||||
size = 0
|
||||
position_idx = None
|
||||
|
||||
if position_idx == 0 and safe_float(size) > 0:
|
||||
await callback_query.answer(
|
||||
text="Торговля уже запущена в одностороннем режиме для данного инструмента"
|
||||
text="У вас есть активная позиция",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -151,12 +72,9 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=True,
|
||||
side=side,
|
||||
)
|
||||
res = await start_trading_cycle(
|
||||
tg_id=callback_query.from_user.id,
|
||||
side=side,
|
||||
switch_side_mode=False,
|
||||
)
|
||||
|
||||
error_messages = {
|
||||
@@ -167,9 +85,10 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
|
||||
"ab not enough for new order": "Недостаточно средств для создания нового ордера",
|
||||
"InvalidRequestError": "Произошла ошибка при запуске торговли.",
|
||||
"Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли",
|
||||
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
|
||||
"position idx not match position mode": "Ошибка режима позиции для данного инструмента",
|
||||
"Qty invalid": "Некорректное значение ордера для данного инструмента",
|
||||
"The number of contracts exceeds maximum limit allowed": "️️Количество контрактов превышает допустимое максимальное количество контрактов",
|
||||
"The number of contracts exceeds minimum limit allowed": "️️Количество контрактов превышает допустимое минимальное количество контрактов",
|
||||
}
|
||||
|
||||
if res == "OK":
|
||||
@@ -180,7 +99,6 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=False,
|
||||
side=side,
|
||||
)
|
||||
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
|
||||
await callback_query.message.edit_text(
|
||||
@@ -202,189 +120,8 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
|
||||
logger.error("Cancelled timer for user %s", callback_query.from_user.id)
|
||||
|
||||
|
||||
@router_start_trading.callback_query(lambda c: c.data == "switch")
|
||||
async def start_trading_switch(
|
||||
callback_query: CallbackQuery, state: FSMContext
|
||||
) -> None:
|
||||
"""
|
||||
Handles the "switch" callback query.
|
||||
Clears the FSM state and sends a message to the user to select the switch side.
|
||||
:param callback_query: Message
|
||||
:param state: FSMContext
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
await state.clear()
|
||||
await callback_query.message.edit_text(
|
||||
text="Выберите направление первой сделки серии:\n\n"
|
||||
"Лонг - открывается первая сделка на покупку.\n"
|
||||
"Шорт - открывается первая сделка на продажу.\n"
|
||||
"По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
|
||||
"Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
|
||||
reply_markup=kbi.switch_side,
|
||||
)
|
||||
except Exception as e:
|
||||
await callback_query.answer(text="Произошла ошибка при запуске торговли")
|
||||
logger.error(
|
||||
"Error processing command start trading switch for user %s: %s",
|
||||
callback_query.from_user.id,
|
||||
e,
|
||||
)
|
||||
|
||||
|
||||
@router_start_trading.callback_query(
|
||||
lambda c: c.data
|
||||
in {"switch_long", "switch_short", "switch_direction", "switch_opposite"}
|
||||
)
|
||||
async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None:
|
||||
"""
|
||||
Starts the trading cycle with the selected side.
|
||||
:param callback_query:
|
||||
:param state:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
|
||||
user_deals_data = await rq.get_user_deal_by_symbol(
|
||||
tg_id=callback_query.from_user.id, symbol=symbol
|
||||
)
|
||||
|
||||
get_side = "Buy"
|
||||
|
||||
if user_deals_data:
|
||||
get_side = user_deals_data.last_side or "Buy"
|
||||
|
||||
if callback_query.data == "switch_long":
|
||||
side = "Buy"
|
||||
elif callback_query.data == "switch_short":
|
||||
side = "Sell"
|
||||
elif callback_query.data == "switch_direction":
|
||||
side = get_side
|
||||
elif callback_query.data == "switch_opposite":
|
||||
if get_side == "Buy":
|
||||
side = "Sell"
|
||||
else:
|
||||
side = "Buy"
|
||||
else:
|
||||
await callback_query.answer(text="Произошла ошибка при запуске торговли")
|
||||
return
|
||||
|
||||
deals = await get_active_positions_by_symbol(
|
||||
tg_id=callback_query.from_user.id, symbol=symbol
|
||||
)
|
||||
position = next((d for d in deals if d.get("symbol") == symbol), None)
|
||||
if position:
|
||||
size = position.get("size", 0)
|
||||
position_idx = position.get("positionIdx")
|
||||
else:
|
||||
size = 0
|
||||
position_idx = None
|
||||
|
||||
if position_idx == 1 and safe_float(size) > 0 and side == "Buy":
|
||||
await callback_query.answer(
|
||||
text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента"
|
||||
)
|
||||
return
|
||||
|
||||
if position_idx == 2 and safe_float(size) > 0 and side == "Sell":
|
||||
await callback_query.answer(
|
||||
text="Торговля уже запущена в режиме хеджирования на продажу для данного инструмента"
|
||||
)
|
||||
return
|
||||
|
||||
conditional_data = await rq.get_user_conditional_settings(
|
||||
tg_id=callback_query.from_user.id
|
||||
)
|
||||
timer_start = conditional_data.timer_start
|
||||
|
||||
cancel_start_task_switch(user_id=callback_query.from_user.id)
|
||||
|
||||
async def delay_start():
|
||||
if timer_start > 0:
|
||||
await callback_query.message.edit_text(
|
||||
text=f"Торговля будет запущена с задержкой {timer_start} мин.",
|
||||
reply_markup=kbi.cancel_timer_switch,
|
||||
)
|
||||
await rq.set_start_timer(
|
||||
tg_id=callback_query.from_user.id, timer_start=0
|
||||
)
|
||||
await asyncio.sleep(timer_start * 60)
|
||||
await rq.set_auto_trading(
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=True,
|
||||
side=side,
|
||||
)
|
||||
if side == "Buy":
|
||||
r_side = "Sell"
|
||||
else:
|
||||
r_side = "Buy"
|
||||
await rq.set_auto_trading(
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=True,
|
||||
side=r_side,
|
||||
)
|
||||
res = await start_trading_cycle(
|
||||
tg_id=callback_query.from_user.id,
|
||||
side=side,
|
||||
switch_side_mode=True,
|
||||
)
|
||||
|
||||
error_messages = {
|
||||
"Limit price is out min price": "Цена лимитного ордера меньше минимального",
|
||||
"Limit price is out max price": "Цена лимитного ордера больше максимального",
|
||||
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
|
||||
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
|
||||
"ab not enough for new order": "Недостаточно средств для создания нового ордера",
|
||||
"InvalidRequestError": "Произошла ошибка при запуске торговли.",
|
||||
"Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли",
|
||||
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
|
||||
"Qty invalid": "Некорректное значение ордера для данного инструмента",
|
||||
"The number of contracts exceeds maximum limit allowed": "️ ️️Количество контрактов превышает допустимое максимальное количество контрактов",
|
||||
}
|
||||
|
||||
if res == "OK":
|
||||
await callback_query.message.edit_text(text="Торговля запущена")
|
||||
await state.clear()
|
||||
else:
|
||||
await rq.set_auto_trading(
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=False,
|
||||
side=side,
|
||||
)
|
||||
if side == "Buy":
|
||||
r_side = "Sell"
|
||||
else:
|
||||
r_side = "Buy"
|
||||
await rq.set_auto_trading(
|
||||
tg_id=callback_query.from_user.id,
|
||||
symbol=symbol,
|
||||
auto_trading=False,
|
||||
side=r_side,
|
||||
)
|
||||
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
|
||||
await callback_query.message.edit_text(
|
||||
text=text, reply_markup=kbi.profile_bybit
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text("Запуск торговли...")
|
||||
task = asyncio.create_task(delay_start())
|
||||
await add_start_task_switch(user_id=callback_query.from_user.id, task=task)
|
||||
except asyncio.CancelledError:
|
||||
logger.error("Cancelled timer for user %s", callback_query.from_user.id)
|
||||
except Exception as e:
|
||||
await callback_query.answer(text="Произошла ошибка при запуске торговли")
|
||||
logger.error(
|
||||
"Error processing command start switch for user %s: %s",
|
||||
callback_query.from_user.id,
|
||||
e,
|
||||
)
|
||||
|
||||
|
||||
@router_start_trading.callback_query(
|
||||
lambda c: c.data == "cancel_timer_merged" or c.data == "cancel_timer_switch"
|
||||
lambda c: c.data == "cancel_timer_merged"
|
||||
)
|
||||
async def cancel_start_trading(
|
||||
callback_query: CallbackQuery, state: FSMContext
|
||||
@@ -400,8 +137,6 @@ async def cancel_start_trading(
|
||||
await state.clear()
|
||||
if callback_query.data == "cancel_timer_merged":
|
||||
cancel_start_task_merged(user_id=callback_query.from_user.id)
|
||||
elif callback_query.data == "cancel_timer_switch":
|
||||
cancel_start_task_switch(user_id=callback_query.from_user.id)
|
||||
await callback_query.message.edit_text(
|
||||
text="Запуск торговли отменен", reply_markup=kbi.profile_bybit
|
||||
)
|
||||
|
Reference in New Issue
Block a user