forked from kodorvan/stcs
Update functions
This commit is contained in:
@@ -1,325 +1,239 @@
|
||||
import time
|
||||
|
||||
from typing import Optional
|
||||
from asyncio import Handle
|
||||
|
||||
from annotated_types import T
|
||||
import asyncio
|
||||
import time
|
||||
import logging
|
||||
from pybit import exceptions
|
||||
from pybit.unified_trading import HTTP
|
||||
from pybit.unified_trading import WebSocket
|
||||
|
||||
from app.services.Bybit.functions import price_symbol
|
||||
import app.services.Bybit.functions.price_symbol as price_symbol
|
||||
import app.services.Bybit.functions.balance as balance_g
|
||||
import app.telegram.database.requests as rq
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
def handle_message(message):
|
||||
print(message)
|
||||
|
||||
async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty):
|
||||
match margin_mode:
|
||||
case 'ISOLATED_MARGIN':
|
||||
margin_mode = 'Isolated'
|
||||
case 'REGULAR_MARGIN':
|
||||
margin_mode = 'Cross'
|
||||
|
||||
human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross'
|
||||
text = f'''Позиция была успешна открыта!
|
||||
Торговая пара: {symbol}
|
||||
Движение: {trade_mode}
|
||||
Тип-маржи: {margin_mode}
|
||||
Кредитное плечо: {leverage}
|
||||
Количество: {qty}
|
||||
'''
|
||||
|
||||
Торговая пара: {symbol}
|
||||
Движение: {trade_mode}
|
||||
Тип-маржи: {human_margin_mode}
|
||||
Кредитное плечо: {leverage}
|
||||
Количество: {qty}
|
||||
'''
|
||||
await message.answer(text=text, parse_mode='html')
|
||||
|
||||
|
||||
async def error_max_step(message):
|
||||
await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок')
|
||||
await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок в серии.')
|
||||
|
||||
|
||||
async def error_max_risk(message):
|
||||
await message.answer('Сделка не была совершена, слишком высокий риск')
|
||||
await message.answer('Сделка не была совершена, риск убытка превышает допустимый лимит.')
|
||||
|
||||
|
||||
async def open_position(tg_id, message, side: str, margin_mode: str):
|
||||
"""
|
||||
Открытие позиции (торговля с мартингейлом и управлением рисками)
|
||||
|
||||
:param tg_id: Telegram ID пользователя
|
||||
:param message: объект сообщения Telegram для ответов
|
||||
:param side: 'Buy' для Long, 'Sell' для Short
|
||||
:param margin_mode: 'Isolated' или 'Cross'
|
||||
"""
|
||||
|
||||
async def contract_long(tg_id, message, margin_mode):
|
||||
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)
|
||||
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id)
|
||||
|
||||
match margin_mode:
|
||||
case 'Isolated':
|
||||
margin_mode = 'ISOLATED_MARGIN'
|
||||
case 'Cross':
|
||||
margin_mode = 'REGULAR_MARGIN'
|
||||
|
||||
client = HTTP(
|
||||
api_key=api_key,
|
||||
api_secret=secret_key
|
||||
)
|
||||
|
||||
try:
|
||||
balance = 0
|
||||
price = 0
|
||||
|
||||
balance = await balance_g.get_balance(tg_id)
|
||||
price = await price_symbol.get_price(tg_id, message)
|
||||
|
||||
client.set_margin_mode(
|
||||
setMarginMode=margin_mode # margin_type
|
||||
)
|
||||
|
||||
martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity
|
||||
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
|
||||
starting_quantity = float(data_main_stgs['starting_quantity'])
|
||||
max_risk_percent = float(data_risk_management_stgs['max_risk_deal'])
|
||||
loss_profit = float(data_risk_management_stgs['price_loss'])
|
||||
takeprofit= float(data_risk_management_stgs['price_profit'])
|
||||
commission_fee = float(data_risk_management_stgs.get('commission_fee', 0))
|
||||
takeProfit_raw = takeprofit
|
||||
takeProfit = takeProfit_raw - commission_fee # уменьшаем TP на комиссию
|
||||
|
||||
if takeProfit < 0:
|
||||
takeProfit = 0
|
||||
|
||||
# Инициализация переменных
|
||||
next_quantity = starting_quantity
|
||||
last_quantity = starting_quantity
|
||||
realised_pnl = 0.0
|
||||
|
||||
current_martingale_step = 0 # Текущая ставка в серии
|
||||
|
||||
next_quantity = 0
|
||||
realised_pnl = 0
|
||||
|
||||
last_quantity = starting_quantity
|
||||
|
||||
# Пример расчёта следующего размера позиции
|
||||
try:
|
||||
position_info = client.get_positions(category='linear', symbol=SYMBOL)
|
||||
position = position_info['result']['list'][0] # или другой нужный индекс
|
||||
|
||||
realised_pnl = float(position['unrealisedPnl'])
|
||||
|
||||
if realised_pnl > 0:
|
||||
starting_quantity = next_quantity
|
||||
current_martingale_step = 0
|
||||
elif not realised_pnl:
|
||||
next_quantity = starting_quantity
|
||||
current_martingale_step += 1
|
||||
else:
|
||||
current_martingale_step += 1
|
||||
next_quantity = last_quantity * martingale_factor
|
||||
starting_quantity = next_quantity
|
||||
except Exception as e:
|
||||
print("Не получены позиции")
|
||||
next_quantity = starting_quantity
|
||||
|
||||
potential_loss = (next_quantity * float(price)) * (loss_profit / 100)
|
||||
allowed_loss = float(balance) * (max_risk_percent / 100)
|
||||
|
||||
if current_martingale_step >= max_martingale_steps:
|
||||
print("Достигнут максимум ставок в серии (8)!")
|
||||
print("Торговля не продолжится")
|
||||
|
||||
await error_max_step(message)
|
||||
else:
|
||||
if potential_loss > allowed_loss:
|
||||
print(f"ОШИБКА: Риск превышен!")
|
||||
print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT")
|
||||
print(f"Разрешено = {allowed_loss} USDT (1% от баланса)")
|
||||
|
||||
await error_max_risk(message)
|
||||
else:
|
||||
print(f"Риск в допустимых пределах. Qty = {next_quantity}")
|
||||
|
||||
r = client.place_order(
|
||||
category='linear',
|
||||
symbol=SYMBOL,
|
||||
side='Buy',
|
||||
orderType="Market",
|
||||
leverage=int(data_main_stgs['size_leverage']),
|
||||
qty=next_quantity,
|
||||
takeProfit=takeProfit, # TP - закрывает позицию, когда цена достигает нужного уровня
|
||||
stopProfit=float(data_risk_management_stgs['price_loss']), # SL - закрывает позицию, когда убыток достигает нужного уровня
|
||||
orderLinkId=f"deal_{SYMBOL}_{time.time()}"
|
||||
)
|
||||
|
||||
await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity)
|
||||
|
||||
except exceptions.InvalidRequestError as e:
|
||||
logging.error(f"Неверно указана торговая пара: {e}")
|
||||
await message.answer('Недостаточно баланса')
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка при совершении сделки: {e}")
|
||||
await message.answer('⚠️ Ошибка при совершении сделки')
|
||||
|
||||
async def contract_short(tg_id, message, margin_mode):
|
||||
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)
|
||||
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id)
|
||||
|
||||
match margin_mode:
|
||||
case 'Isolated':
|
||||
margin_mode = 'ISOLATED_MARGIN'
|
||||
case 'Cross':
|
||||
margin_mode = 'REGULAR_MARGIN'
|
||||
|
||||
client = HTTP(
|
||||
api_key=api_key,
|
||||
api_secret=secret_key
|
||||
)
|
||||
|
||||
try:
|
||||
balance = 0
|
||||
price = 0
|
||||
|
||||
balance = await balance_g.get_balance(tg_id)
|
||||
price = await price_symbol.get_price(tg_id, message)
|
||||
|
||||
client.set_margin_mode(
|
||||
setMarginMode=margin_mode # margin_type
|
||||
)
|
||||
|
||||
martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity
|
||||
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
|
||||
starting_quantity = float(data_main_stgs['starting_quantity'])
|
||||
max_risk_percent = float(data_risk_management_stgs['max_risk_deal'])
|
||||
loss_profit = float(data_risk_management_stgs['price_loss'])
|
||||
takeprofit = float(data_risk_management_stgs['price_profit'])
|
||||
commission_fee = float(data_risk_management_stgs.get('commission_fee', 0))
|
||||
takeProfit_raw = takeprofit
|
||||
takeProfit = takeProfit_raw - commission_fee # уменьшаем TP на комиссию
|
||||
|
||||
if takeProfit < 0:
|
||||
takeProfit = 0
|
||||
|
||||
# Инициализация переменных
|
||||
next_quantity = starting_quantity
|
||||
last_quantity = starting_quantity
|
||||
realised_pnl = 0.0
|
||||
|
||||
current_martingale_step = 0 # Текущая ставка в серии
|
||||
|
||||
next_quantity = 0
|
||||
realised_pnl = 0
|
||||
|
||||
last_quantity = starting_quantity
|
||||
|
||||
# Пример расчёта следующего размера позиции
|
||||
try:
|
||||
position_info = client.get_positions(category='linear', symbol=SYMBOL)
|
||||
position = position_info['result']['list'][0] # или другой нужный индекс
|
||||
|
||||
realised_pnl = float(position['unrealisedPnl'])
|
||||
|
||||
if realised_pnl > 0:
|
||||
starting_quantity = next_quantity
|
||||
current_martingale_step = 0
|
||||
elif not realised_pnl:
|
||||
next_quantity = starting_quantity
|
||||
current_martingale_step += 1
|
||||
else:
|
||||
current_martingale_step += 1
|
||||
next_quantity = last_quantity * martingale_factor
|
||||
starting_quantity = next_quantity
|
||||
except Exception as e:
|
||||
print("Не получены позиции")
|
||||
next_quantity = starting_quantity
|
||||
|
||||
potential_loss = (next_quantity * float(price)) * (loss_profit / 100)
|
||||
allowed_loss = float(balance) * (max_risk_percent / 100)
|
||||
|
||||
if current_martingale_step >= max_martingale_steps:
|
||||
print("Достигнут максимум ставок в серии (8)!")
|
||||
print("Торговля не продолжится")
|
||||
|
||||
await error_max_step(message)
|
||||
else:
|
||||
if potential_loss > allowed_loss:
|
||||
print(f"ОШИБКА: Риск превышен!")
|
||||
print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT")
|
||||
print(f"Разрешено = {allowed_loss} USDT (1% от баланса)")
|
||||
|
||||
await error_max_risk(message)
|
||||
else:
|
||||
print(f"Риск в допустимых пределах. Qty = {next_quantity}")
|
||||
|
||||
r = client.place_order(
|
||||
category='linear',
|
||||
symbol=SYMBOL,
|
||||
side='Sell',
|
||||
orderType="Market",
|
||||
leverage=int(data_main_stgs['size_leverage']),
|
||||
qty=next_quantity,
|
||||
orderLinkId=f"deal_{SYMBOL}_{time.time()}"
|
||||
)
|
||||
|
||||
await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity)
|
||||
|
||||
except exceptions.InvalidRequestError as e:
|
||||
logging.error(f"Error in open_deal: {e}")
|
||||
await message.answer('Недостаточно баланса')
|
||||
except Exception as e:
|
||||
logging.error(f"Error in open_deal: {e}")
|
||||
await message.answer('⚠️ Ошибка при совершении сделки')
|
||||
|
||||
|
||||
|
||||
async def open_market_order(tg_id, message, api_key, secret_key):
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
trading_mode = data_main_stgs['trading_mode']
|
||||
margin_mode = data_main_stgs.get('margin_type')
|
||||
|
||||
if trading_mode == 'Long':
|
||||
await contract_long(tg_id, message, margin_mode)
|
||||
elif trading_mode == 'Short':
|
||||
await contract_short(tg_id, message, margin_mode)
|
||||
elif trading_mode == 'Smart':
|
||||
await message.answer("Режим Smart пока недоступен")
|
||||
elif trading_mode == 'Switch':
|
||||
await message.answer("Режим Switch пока недоступен")
|
||||
|
||||
|
||||
async def open_limit_order(tg_id, message, price, api_key, secret_key):
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
trading_mode = data_main_stgs['trading_mode']
|
||||
margin_mode = data_main_stgs.get('margin_type')
|
||||
order_type = await rq.get_entry_order_type(tg_id)
|
||||
|
||||
|
||||
client = HTTP(
|
||||
api_key=api_key,
|
||||
api_secret=secret_key
|
||||
)
|
||||
|
||||
symbol = await rq.get_symbol(tg_id)
|
||||
qty = float(data_main_stgs['starting_quantity'])
|
||||
side = 'Buy' if trading_mode == 'Long' else 'Short'
|
||||
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
data_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
|
||||
|
||||
bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
|
||||
|
||||
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||||
|
||||
try:
|
||||
balance = await balance_g.get_balance(tg_id)
|
||||
price = await price_symbol.get_price(tg_id, message)
|
||||
|
||||
# Установка маржинального режима
|
||||
client.set_margin_mode(setMarginMode=bybit_margin_mode)
|
||||
|
||||
martingale_factor = float(data_main_stgs['martingale_factor'])
|
||||
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
|
||||
starting_quantity = float(data_main_stgs['starting_quantity'])
|
||||
max_risk_percent = float(data_risk_stgs['max_risk_deal'])
|
||||
loss_profit = float(data_risk_stgs['price_loss'])
|
||||
takeprofit = float(data_risk_stgs['price_profit'])
|
||||
commission_fee = float(data_risk_stgs.get('commission_fee', 0))
|
||||
|
||||
takeProfit = max(takeprofit - commission_fee, 0)
|
||||
|
||||
current_martingale_step = 0
|
||||
next_quantity = starting_quantity
|
||||
last_quantity = starting_quantity
|
||||
realised_pnl = 0.0
|
||||
|
||||
# Получаем текущие открытые позиции по символу
|
||||
positions_resp = client.get_positions(category='linear', symbol=symbol)
|
||||
positions_list = positions_resp.get('result', {}).get('list', [])
|
||||
current_martingale_step = await rq.get_martingale_step(tg_id)
|
||||
if positions_list:
|
||||
position = positions_list[0]
|
||||
realised_pnl = float(position.get('unrealisedPnl', 0.0))
|
||||
|
||||
|
||||
if realised_pnl > 0:
|
||||
current_martingale_step = 0
|
||||
next_quantity = starting_quantity
|
||||
|
||||
else:
|
||||
current_martingale_step += 1
|
||||
if current_martingale_step > max_martingale_steps:
|
||||
await error_max_step(message)
|
||||
return
|
||||
next_quantity = starting_quantity * (martingale_factor ** current_martingale_step)
|
||||
else:
|
||||
# Позиция не открыта — начинаем с начальной ставки
|
||||
next_quantity = starting_quantity
|
||||
current_martingale_step = 0
|
||||
|
||||
# Проверяем риск убытка
|
||||
potential_loss = next_quantity * price * (loss_profit / 100)
|
||||
allowed_loss = balance * (max_risk_percent / 100)
|
||||
|
||||
if potential_loss > allowed_loss:
|
||||
await error_max_risk(message)
|
||||
return
|
||||
|
||||
# Отправляем запрос на открытие ордера
|
||||
response = client.place_order(
|
||||
category='linear',
|
||||
symbol=symbol,
|
||||
side=side,
|
||||
orderType='Limit',
|
||||
qty=qty,
|
||||
price=price,
|
||||
timeInForce='GTC',
|
||||
orderLinkId=f"order_{int(time.time())}"
|
||||
orderType="Market",
|
||||
qty=next_quantity,
|
||||
leverage=int(data_main_stgs['size_leverage']),
|
||||
takeProfit=takeProfit,
|
||||
stopLoss=loss_profit,
|
||||
orderLinkId=f"deal_{symbol}_{int(time.time())}"
|
||||
)
|
||||
if response.get('retCode') == 0:
|
||||
await message.answer(f"Limit ордер открыт: {side} {qty} {symbol} по цене {price}")
|
||||
await rq.update_user_trades(tg_id, symbol=symbol, side=order_type)
|
||||
|
||||
if response.get('ret_code', -1) == 0:
|
||||
await info_access_open_deal(message, symbol, data_main_stgs['trading_mode'], bybit_margin_mode,
|
||||
data_main_stgs['size_leverage'], next_quantity)
|
||||
await rq.update_martingale_step(tg_id, current_martingale_step)
|
||||
else:
|
||||
await message.answer(f"Ошибка открытия ордера: {response.get('retMsg')}")
|
||||
await message.answer(f"Ошибка открытия ордера: {response.get('ret_msg', 'неизвестная ошибка')}")
|
||||
|
||||
except exceptions.InvalidRequestError as e:
|
||||
logging.error(f"InvalidRequestError: {e}")
|
||||
await message.answer('Ошибка: неверно указана торговая пара или параметры.')
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка при открытии лимитного ордера: {e}")
|
||||
await message.answer("Ошибка при открытии ордера")
|
||||
logging.error(f"Ошибка при совершении сделки: {e}")
|
||||
await message.answer('⚠️ Ошибка при совершении сделки')
|
||||
|
||||
|
||||
async def trading_cycle(tg_id, message):
|
||||
start_time = time.time()
|
||||
timer_min = await rq.get_user_timer(tg_id)
|
||||
timer_sec = timer_min * 60 if timer_min else 0
|
||||
|
||||
while True:
|
||||
elapsed = time.time() - start_time
|
||||
if timer_sec > 0 and elapsed > timer_sec:
|
||||
await message.answer("Время работы по таймеру истекло. Торговля остановлена.")
|
||||
await rq.update_martingale_step(tg_id, 0)
|
||||
break
|
||||
|
||||
# Проверяем позиции
|
||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||
side = 'Buy' if data_main_stgs['trading_mode'] == 'Long' else 'Sell'
|
||||
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
|
||||
|
||||
# Можно добавлять логику по PNL, стоп-лоссам, тейк-профитам
|
||||
|
||||
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
|
||||
|
||||
async def get_active_positions(message, api_key, secret_key, symbol):
|
||||
|
||||
client = HTTP(
|
||||
api_key=api_key,
|
||||
api_secret=secret_key
|
||||
)
|
||||
instruments_resp = client.get_instruments_info(category='linear')
|
||||
if instruments_resp.get('ret_code') != 0:
|
||||
return []
|
||||
symbols = [item['symbol'] for item in instruments_resp.get('result', {}).get('list', [])]
|
||||
|
||||
active_positions = []
|
||||
|
||||
async def fetch_positions(symbol):
|
||||
try:
|
||||
resp = client.get_positions(category='linear', symbol=symbol)
|
||||
if resp.get('ret_code') == 0:
|
||||
positions = resp.get('result', {}).get('list', [])
|
||||
for pos in positions:
|
||||
if pos.get('size') and float(pos['size']) > 0:
|
||||
active_positions.append(pos)
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка при получении позиций: {e}")
|
||||
await message.answer('⚠️ Ошибка при получении позиций')
|
||||
|
||||
for sym in symbols:
|
||||
await fetch_positions(sym)
|
||||
|
||||
return active_positions
|
||||
|
||||
|
||||
async def close_user_trade(tg_id: int, symbol: str) -> bool:
|
||||
api_key = await rq.get_bybit_api_key(tg_id)
|
||||
secret_key = await rq.get_bybit_secret_key(tg_id)
|
||||
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||||
|
||||
# Получаем текущие открытые позиции по символу (пример для linear фьючерсов)
|
||||
positions_resp = client.get_positions(category="linear", symbol=symbol)
|
||||
|
||||
ret_code = positions_resp.get('ret_code')
|
||||
result = positions_resp.get('result')
|
||||
|
||||
if ret_code != 0 or not result or not result.get('list'):
|
||||
return False
|
||||
|
||||
positions_list = result['list']
|
||||
if not positions_list:
|
||||
return False
|
||||
|
||||
position = positions_list[0]
|
||||
qty = abs(float(position['size']))
|
||||
side = position['side']
|
||||
|
||||
if qty == 0:
|
||||
return False
|
||||
|
||||
# Определяем сторону закрытия — противоположная открытой позиции
|
||||
close_side = "Sell" if side == "Buy" else "Buy"
|
||||
|
||||
try:
|
||||
response = client.place_order(
|
||||
category="linear",
|
||||
symbol=symbol,
|
||||
side=close_side,
|
||||
orderType="Market",
|
||||
qty=str(qty),
|
||||
timeInForce="GoodTillCancel",
|
||||
reduceOnly=True
|
||||
)
|
||||
return response['ret_code'] == 0
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_positive_percent(negative_percent: float, manual_positive_percent: float | None) -> float:
|
||||
if manual_positive_percent and manual_positive_percent > 0:
|
||||
return manual_positive_percent
|
||||
return abs(negative_percent)
|
Reference in New Issue
Block a user