Compare commits

...

52 Commits

Author SHA1 Message Date
88c358b90e Merge pull request 'Fixed websocket' (#29) from Alex/stcs:devel into stable
Reviewed-on: #29
2025-11-02 19:55:17 +07:00
algizn97
3ba32660ea The log is hidden 2025-11-02 17:33:11 +05:00
algizn97
e0167ea406 Fixed the function of setting TP and SL 2025-11-02 17:14:22 +05:00
algizn97
dbf0a30d54 The message receipt has been sorted 2025-11-02 17:13:36 +05:00
a7a23a4662 Merge pull request 'Added error information output' (#28) from Alex/stcs:devel into stable
Reviewed-on: #28
2025-10-30 23:37:38 +07:00
algizn97
78f21e6718 Fixed message output for certain conditions 2025-10-30 13:47:29 +05:00
algizn97
6416bd6dc9 Added output in case of installation error TP and SL 2025-10-30 13:21:22 +05:00
algizn97
29c168e31d Added error information output 2025-10-30 12:45:22 +05:00
245cadf650 Merge pull request 'devel' (#27) from Alex/stcs:devel into stable
Reviewed-on: #27
2025-10-30 12:28:18 +07:00
algizn97
e043a2429f The logic of setting take profit and stop loss has been changed, added data for the end user 2025-10-29 20:58:04 +05:00
algizn97
a8119d2811 adjusted percentages of TP and SL 2025-10-29 20:55:51 +05:00
algizn97
7e4c936ef5 Added pnl, tp and sl column 2025-10-29 20:54:29 +05:00
algizn97
d8866af185 Added pnl, tp and sl column 2025-10-29 20:54:20 +05:00
8d32439a15 Merge pull request 'Switched to demo mode' (#26) from Alex/stcs:devel into stable
Reviewed-on: #26
2025-10-27 21:45:00 +07:00
algizn97
9497cca3e0 Switched to demo mode 2025-10-27 13:05:19 +05:00
690d793e8c Merge pull request 'Fixed the counting of the series, added a request to set the serial number' (#25) from Alex/stcs:devel into stable
Reviewed-on: #25
2025-10-26 22:09:25 +07:00
algizn97
a0ef48810a Fixed the counting of the series, added a request to set the serial number 2025-10-26 19:44:16 +05:00
ab752b5dd8 Merge pull request 'adjusted profit' (#24) from Alex/stcs:devel into stable
Reviewed-on: #24
2025-10-26 21:18:20 +07:00
algizn97
a2164853d9 adjusted profit 2025-10-26 19:17:38 +05:00
algizn97
92bb052151 adjusted profit 2025-10-26 19:11:37 +05:00
ca7bd5c795 Merge pull request 'Shtoto' (#23) from Alex/stcs:devel into stable
Reviewed-on: #23
2025-10-26 16:39:41 +07:00
algizn97
62e923cefa Fixed the place for commission compensation 2025-10-26 14:03:28 +05:00
algizn97
faae2475c1 Added a function to select the type of cost compensation. 2025-10-26 14:02:42 +05:00
algizn97
3ef8eae997 Added the output of the current series number and the transaction 2025-10-26 14:01:55 +05:00
algizn97
2bebada215 Added a new column and requests to them 2025-10-26 14:01:12 +05:00
algizn97
8be1636279 Added buttons to select commission compensation 2025-10-26 14:00:32 +05:00
algizn97
b756aadf26 Added columns for commission 2025-10-26 13:59:50 +05:00
46c890f7af Merge pull request 'The range of TP and SL settings has been changed' (#22) from Alex/stcs:devel into stable
Reviewed-on: #22
2025-10-25 23:43:24 +07:00
algizn97
f10500cc79 The range of TP and SL settings has been changed 2025-10-25 21:35:44 +05:00
2d7acb491e Merge pull request 'разъебаться по полной' (#21) from Alex/stcs:devel into stable
Reviewed-on: #21
2025-10-25 21:05:58 +07:00
algizn97
d767399988 Added a function to set the direction of the first transaction 2025-10-25 18:49:36 +05:00
algizn97
89603f0b62 Fixed the direction at the start of the series 2025-10-25 18:49:06 +05:00
algizn97
14f2a9e773 Added the display of the first transaction 2025-10-25 18:48:25 +05:00
algizn97
a43fc6a66b Added the side parameter and the request 2025-10-25 18:47:07 +05:00
algizn97
869458b2e1 Added a button to select the direction of the first transaction. 2025-10-25 18:46:26 +05:00
algizn97
07948d93cf Added a new migration for the database 2025-10-25 18:45:42 +05:00
12d1db16d3 вот бы ебануло нормально...
Reviewed-on: #20
2025-10-25 19:54:27 +07:00
algizn97
7350c86927 The text has been corrected, fixed the commission check 2025-10-25 17:50:46 +05:00
0a369b10f2 Merge pull request 'devel' (#19) from Alex/stcs:devel into stable
Reviewed-on: #19
2025-10-23 14:35:32 +07:00
algizn97
42f0f8ddc0 The text has been corrected 2025-10-23 11:31:15 +05:00
algizn97
3df88d07ab The stop trading button has been added, and the switch mode has been fixed 2025-10-23 11:28:44 +05:00
7b1a803db4 Merge pull request 'Fixed the switch trading mode, adjusted the take profit, added a trading cycle' (#18) from Alex/stcs:devel into stable
Reviewed-on: #18
2025-10-22 22:20:21 +07:00
algizn97
ddfa3a7360 Fixed the switch trading mode, adjusted the take profit, added a trading cycle 2025-10-22 17:15:25 +05:00
9fcd92cc72 Merge pull request 'The formula for calculating the number of contracts by price has been changed' (#17) from Alex/stcs:devel into stable
Reviewed-on: #17
2025-10-21 20:33:42 +07:00
algizn97
e61b7334a4 The formula for calculating the number of contracts by price has been changed 2025-10-21 13:59:09 +05:00
97a199f31e Merge pull request 'devel' (#16) from Alex/stcs:devel into stable
Reviewed-on: #16
2025-10-18 18:09:23 +07:00
algizn97
5ad69f3f6d Fixed take profit calculation. Added a position check for the current pair when trying to change the margin. 2025-10-18 13:52:20 +05:00
algizn97
abad01352a Fixed the output 2025-10-17 11:28:57 +05:00
algizn97
720b30d681 Redundant call removed 2025-10-17 11:13:31 +05:00
algizn97
3616e2cbd3 Added verification for open orders. Adjusted responses for the user 2025-10-17 11:12:50 +05:00
algizn97
7d108337fa Fixed receiving the commission and calculating the total commission 2025-10-17 11:10:35 +05:00
algizn97
0f6e6a2168 Added position mode setting, fixed stop loss calculation 2025-10-17 11:09:21 +05:00
25 changed files with 1294 additions and 462 deletions

View File

@@ -54,6 +54,10 @@ sudo -u www-data /usr/bin/pip install -r requirements.txt
cp .env.sample .env
nvim .env
```
5. Выполните миграции:
```bash
alembic upgrade head
```
5. Запустите бота:

View File

@@ -0,0 +1,32 @@
"""Added column current_series
Revision ID: 0ee52ab23e66
Revises: e5d612e44563
Create Date: 2025-10-26 11:48:48.055031
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '0ee52ab23e66'
down_revision: Union[str, Sequence[str], None] = 'e5d612e44563'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('current_series', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'current_series')
# ### end Alembic commands ###

View File

@@ -0,0 +1,44 @@
"""Added TP_SL and PNL
Revision ID: 3fca121b7554
Revises: adf3d2991896
Create Date: 2025-10-29 11:07:45.350771
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = '3fca121b7554'
down_revision: Union[str, Sequence[str], None] = 'adf3d2991896'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
conn = op.get_bind()
inspector = inspect(conn)
columns = [col['name'] for col in inspector.get_columns('user_deals')]
if 'pnl_series' not in columns:
op.add_column('user_deals', sa.Column('pnl_series', sa.Float(), nullable=True))
if 'take_profit' not in columns:
op.add_column('user_deals', sa.Column('take_profit', sa.Boolean(), nullable=False, server_default=sa.false()))
if 'stop_loss' not in columns:
op.add_column('user_deals', sa.Column('stop_loss', sa.Boolean(), nullable=False, server_default=sa.false()))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'stop_loss')
op.drop_column('user_deals', 'take_profit')
op.drop_column('user_deals', 'pnl_series')
# ### end Alembic commands ###

View File

@@ -0,0 +1,49 @@
"""Fixed TP_SL type
Revision ID: 8329c0994b26
Revises: 3fca121b7554
Create Date: 2025-10-29 13:07:52.161139
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8329c0994b26'
down_revision: Union[str, Sequence[str], None] = '3fca121b7554'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
# Добавляем новую колонку с нужным типом
batch_op.add_column(sa.Column('take_profit_new', sa.Float(), nullable=False, server_default='0'))
# После закрытия batch создается и переименовывается таблица.
# Теперь мы можем обновить данные.
op.execute(
"UPDATE user_deals SET take_profit_new = CAST(take_profit AS FLOAT)"
)
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
# Удаляем старую колонку
batch_op.drop_column('take_profit')
# Меняем имя новой колонки на старое
batch_op.alter_column('take_profit_new', new_column_name='take_profit')
def downgrade():
# Аналогично, но в обратном порядке и типе
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
batch_op.add_column(sa.Column('take_profit_old', sa.Boolean(), nullable=False, server_default='0'))
op.execute(
"UPDATE user_deals SET take_profit_old = CAST(take_profit AS BOOLEAN)"
)
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
batch_op.drop_column('take_profit')
batch_op.alter_column('take_profit_old', new_column_name='take_profit')

View File

@@ -0,0 +1,45 @@
"""Added column commission_place
Revision ID: adf3d2991896
Revises: 0ee52ab23e66
Create Date: 2025-10-26 13:37:33.662318
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = 'adf3d2991896'
down_revision: Union[str, Sequence[str], None] = '0ee52ab23e66'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
bind = op.get_bind()
inspector = inspect(bind)
columns_user_deals = [col['name'] for col in inspector.get_columns('user_deals')]
if 'commission_fee' not in columns_user_deals:
op.add_column('user_deals', sa.Column('commission_fee', sa.String(), server_default='', nullable=True))
if 'commission_place' not in columns_user_deals:
op.add_column('user_deals', sa.Column('commission_place', sa.String(), server_default='', nullable=True))
columns_user_risk_mgmt = [col['name'] for col in inspector.get_columns('user_risk_management')]
if 'commission_place' not in columns_user_risk_mgmt:
op.add_column('user_risk_management',
sa.Column('commission_place', sa.String(), server_default='', nullable=False))
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_risk_management', 'commission_place')
op.drop_column('user_deals', 'commission_place')
op.drop_column('user_deals', 'commission_fee')
# ### end Alembic commands ###

View File

@@ -0,0 +1,34 @@
"""Added column side for additional_setiings
Revision ID: e5d612e44563
Revises: fbf4e3658310
Create Date: 2025-10-25 18:25:52.746250
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'e5d612e44563'
down_revision: Union[str, Sequence[str], None] = 'fbf4e3658310'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_additional_settings',
sa.Column('side', sa.String(), nullable=False, server_default='')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_additional_settings', 'side')
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""Added side_mode column
Revision ID: fbf4e3658310
Revises:
Create Date: 2025-10-22 13:08:02.317419
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'fbf4e3658310'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('side_mode', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'side_mode')
# ### end Alembic commands ###

View File

@@ -15,7 +15,7 @@ async def get_bybit_client(tg_id: int) -> HTTP | None:
"""
try:
api_key, api_secret = await rq.get_user_api(tg_id=tg_id)
return HTTP(api_key=api_key, api_secret=api_secret)
return HTTP(demo=True, api_key=api_key, api_secret=api_secret)
except Exception as e:
logger.error("Error getting bybit client for user %s: %s", tg_id, e)
return None

View File

@@ -7,25 +7,25 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("close_positions")
async def close_position(
tg_id: int, symbol: str, side: str, position_idx: int, qty: float
async def close_position_by_symbol(
tg_id: int, symbol: str
) -> bool:
"""
Closes all positions
:param tg_id: Telegram user ID
:param symbol: symbol
:param side: side
:param position_idx: position index
:param qty: quantity
:return: bool
"""
try:
client = await get_bybit_client(tg_id)
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
response = client.get_positions(
category="linear", symbol=symbol
)
positions = response.get("result", {}).get("list", [])
r_side = "Sell" if positions[0].get("side") == "Buy" else "Buy"
qty = positions[0].get("size")
position_idx = positions[0].get("positionIdx")
response = client.place_order(
category="linear",
@@ -37,16 +37,16 @@ async def close_position(
positionIdx=position_idx,
)
if response["retCode"] == 0:
logger.info("All positions closed for %s for user %s", symbol, tg_id)
logger.info("Positions closed for %s for user %s", symbol, tg_id)
return True
else:
logger.error(
"Error closing all positions for %s for user %s", symbol, tg_id
"Error closing position for %s for user %s", symbol, tg_id
)
return False
except Exception as e:
logger.error(
"Error closing all positions for %s for user %s: %s", symbol, tg_id, e
"Error closing positions for %s for user %s: %s", symbol, tg_id, e
)
return False

View File

@@ -10,30 +10,27 @@ from app.bybit.get_functions.get_tickers import get_tickers
from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
from app.bybit.set_functions.set_leverage import set_leverage
from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.helper_functions import get_liquidation_price, safe_float
from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode
from app.helper_functions import safe_float
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("open_positions")
async def start_trading_cycle(
tg_id: int
tg_id: int
) -> str | None:
"""
Start trading cycle
:param tg_id: Telegram user ID
"""
try:
client = await get_bybit_client(tg_id=tg_id)
symbol = await rq.get_user_symbol(tg_id=tg_id)
additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
trade_mode = additional_data.trade_mode
switch_side = additional_data.switch_side
side = additional_data.side
margin_type = additional_data.margin_type
leverage = additional_data.leverage
order_quantity = additional_data.order_quantity
@@ -42,59 +39,41 @@ async def start_trading_cycle(
max_bets_in_series = additional_data.max_bets_in_series
take_profit_percent = risk_management_data.take_profit_percent
stop_loss_percent = risk_management_data.stop_loss_percent
get_side = "Buy"
if user_deals_data:
get_side = user_deals_data.last_side or "Buy"
commission_fee = risk_management_data.commission_fee
commission_place = risk_management_data.commission_place
if trade_mode == "Switch":
if switch_side == "По направлению":
side = get_side
else:
if get_side == "Buy":
side = "Sell"
else:
side = "Buy"
side = side
else:
if trade_mode == "Long":
side = "Buy"
else:
side = "Sell"
# Get fee rates
fee_info = client.get_fee_rates(category="linear", symbol=symbol)
# Check if commission fee is enabled
commission_fee_percent = 0.0
if commission_fee == "Yes_commission_fee":
commission_fee_percent = safe_float(
fee_info["result"]["list"][0]["takerFeeRate"]
)
get_ticker = await get_tickers(tg_id, symbol=symbol)
price_symbol = safe_float(get_ticker.get("lastPrice")) or 0
instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep")
qty_step = safe_float(qty_step_str)
qty = safe_float(order_quantity) / safe_float(price_symbol)
decimals = abs(int(round(math.log10(qty_step))))
qty_formatted = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_formatted, decimals)
if trigger_price > 0:
po_trigger_price = str(trigger_price)
else:
po_trigger_price = None
price_for_cals = trigger_price if po_trigger_price is not None else price_symbol
total_commission = price_for_cals * qty_formatted * commission_fee_percent
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
await set_switch_position_mode(
tg_id=tg_id,
symbol=symbol,
mode=0)
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
current_step=1,
current_series=1,
trade_mode=trade_mode,
side_mode=switch_side,
margin_type=margin_type,
leverage=leverage,
order_quantity=order_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=order_quantity,
commission_fee=commission_fee,
commission_place=commission_place,
pnl_series=0
)
res = await open_positions(
@@ -103,47 +82,28 @@ async def start_trading_cycle(
side=side,
order_quantity=order_quantity,
trigger_price=trigger_price,
margin_type=margin_type,
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
commission_fee_percent=total_commission
leverage=leverage
)
if res == "OK":
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
last_side=side,
current_step=1,
trade_mode=trade_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=order_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=order_quantity
)
return "OK"
return (
res
if res
in {
"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",
"The number of contracts exceeds minimum limit allowed"
}
in {
"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",
"The number of contracts exceeds minimum limit allowed",
"Order placement failed as your position may exceed the max",
}
else None
)
@@ -152,12 +112,99 @@ async def start_trading_cycle(
return None
async def trading_cycle_profit(
tg_id: int, symbol: str, side: str) -> str | None:
try:
user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
trade_mode = user_deals_data.trade_mode
margin_type = user_deals_data.margin_type
leverage = user_deals_data.leverage
trigger_price = 0
take_profit_percent = user_deals_data.take_profit_percent
stop_loss_percent = user_deals_data.stop_loss_percent
max_bets_in_series = user_deals_data.max_bets_in_series
martingale_factor = user_deals_data.martingale_factor
side_mode = user_deals_data.side_mode
base_quantity = user_deals_data.base_quantity
current_series = user_deals_data.current_series
commission_fee = user_deals_data.commission_fee
commission_place = user_deals_data.commission_place
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
tg_id=tg_id,
symbol=symbol,
leverage=leverage,
)
if trade_mode == "Switch":
if side_mode == "Противоположно":
s_side = "Sell" if side == "Buy" else "Buy"
else:
s_side = side
else:
s_side = side
next_series = current_series + 1
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
current_step=1,
current_series=next_series,
trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=base_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity,
commission_fee=commission_fee,
commission_place=commission_place,
pnl_series=0
)
res = await open_positions(
tg_id=tg_id,
symbol=symbol,
side=s_side,
order_quantity=base_quantity,
trigger_price=trigger_price,
leverage=leverage
)
if res == "OK":
return "OK"
return (
res
if res
in {
"Risk is too high for this trade",
"ab not enough for new order",
"InvalidRequestError",
"The number of contracts exceeds maximum limit allowed",
"Order placement failed as your position may exceed the max",
}
else None
)
except Exception as e:
logger.error("Error in trading_cycle_profit: %s", e)
return None
async def trading_cycle(
tg_id: int, symbol: str, reverse_side: str
tg_id: int, symbol: str, side: str,
) -> str | None:
try:
user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol)
commission_fee = user_deals_data.commission_fee
commission_place = user_deals_data.commission_place
total_fee = user_auto_trading_data.total_fee
trade_mode = user_deals_data.trade_mode
margin_type = user_deals_data.margin_type
@@ -170,23 +217,9 @@ async def trading_cycle(
current_step = user_deals_data.current_step
order_quantity = user_deals_data.order_quantity
base_quantity = user_deals_data.base_quantity
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
tg_id=tg_id,
symbol=symbol,
leverage=leverage,
)
if reverse_side == "Buy":
real_side = "Sell"
else:
real_side = "Buy"
side = real_side
if trade_mode == "Switch":
side = "Sell" if real_side == "Buy" else "Buy"
side_mode = user_deals_data.side_mode
current_series = user_deals_data.current_series
pnl_series = user_deals_data.pnl_series
next_quantity = safe_float(order_quantity) * (
safe_float(martingale_factor)
@@ -196,47 +229,68 @@ async def trading_cycle(
if max_bets_in_series < current_step:
return "Max bets in series"
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
tg_id=tg_id,
symbol=symbol,
leverage=leverage,
)
total_quantity = next_quantity
if commission_fee == "Yes_commission_fee":
if commission_place == "Commission_for_qty":
total_quantity = next_quantity + total_fee
if trade_mode == "Switch":
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
else:
r_side = side
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
current_step=current_step,
current_series=current_series,
trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=next_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity,
commission_fee=commission_fee,
commission_place=commission_place,
pnl_series=pnl_series
)
res = await open_positions(
tg_id=tg_id,
symbol=symbol,
side=side,
order_quantity=next_quantity,
side=r_side,
order_quantity=total_quantity,
trigger_price=trigger_price,
margin_type=margin_type,
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
commission_fee_percent=total_fee
leverage=leverage
)
if res == "OK":
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
last_side=side,
current_step=current_step,
trade_mode=trade_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=next_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity
)
return "OK"
return (
res
if res
in {
"Risk is too high for this trade",
"ab not enough for new order",
"InvalidRequestError",
"The number of contracts exceeds maximum limit allowed",
}
in {
"Risk is too high for this trade",
"ab not enough for new order",
"InvalidRequestError",
"The number of contracts exceeds maximum limit allowed",
"Order placement failed as your position may exceed the max",
}
else None
)
@@ -246,28 +300,29 @@ async def trading_cycle(
async def open_positions(
tg_id: int,
side: str,
symbol: str,
order_quantity: float,
trigger_price: float,
margin_type: str,
leverage: str,
take_profit_percent: float,
stop_loss_percent: float,
commission_fee_percent: float
tg_id: int,
side: str,
symbol: str,
order_quantity: float,
trigger_price: float,
leverage: str
) -> str | None:
try:
client = await get_bybit_client(tg_id=tg_id)
get_ticker = await get_tickers(tg_id, symbol=symbol)
price_symbol = safe_float(get_ticker.get("lastPrice")) or 0
if get_ticker is None:
price_symbol = 0
else:
price_symbol = safe_float(get_ticker.get("lastPrice"))
instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep")
qty_step = safe_float(qty_step_str)
qty = safe_float(order_quantity) / safe_float(price_symbol)
qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(price_symbol)
decimals = abs(int(round(math.log10(qty_step))))
qty_formatted = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_formatted, decimals)
qty_format = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_format, decimals)
if trigger_price > 0:
po_trigger_price = str(trigger_price)
@@ -276,41 +331,6 @@ async def open_positions(
po_trigger_price = None
trigger_direction = None
get_leverage = safe_float(leverage)
price_for_cals = trigger_price if po_trigger_price is not None else price_symbol
if margin_type == "ISOLATED_MARGIN":
liq_long, liq_short = await get_liquidation_price(
tg_id=tg_id,
entry_price=price_for_cals,
symbol=symbol,
leverage=get_leverage,
)
if (liq_long > 0 or liq_short > 0) and price_for_cals > 0:
if side == "Buy":
base_tp = price_for_cals + (price_for_cals - liq_long)
take_profit_price = base_tp + commission_fee_percent
else:
base_tp = price_for_cals - (liq_short - price_for_cals)
take_profit_price = base_tp - commission_fee_percent
take_profit_price = max(take_profit_price, 0)
else:
take_profit_price = None
stop_loss_price = None
else:
if side == "Buy":
take_profit_price = price_for_cals * (1 + take_profit_percent / 100) + commission_fee_percent
stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100) - commission_fee_percent
else:
take_profit_price = price_for_cals * (1 - take_profit_percent / 100) - commission_fee_percent
stop_loss_price = price_for_cals * (1 + stop_loss_percent / 100) + commission_fee_percent
take_profit_price = max(take_profit_price, 0)
stop_loss_price = max(stop_loss_price, 0)
# Place order
order_params = {
"category": "linear",
@@ -324,8 +344,6 @@ async def open_positions(
"timeInForce": "GTC",
"positionIdx": 0,
"tpslMode": "Full",
"takeProfit": str(take_profit_price) if take_profit_price else None,
"stopLoss": str(stop_loss_price) if stop_loss_price else None,
}
response = client.place_order(**order_params)
@@ -347,6 +365,7 @@ async def open_positions(
"Qty invalid": "Qty invalid",
"The number of contracts exceeds maximum limit allowed": "The number of contracts exceeds maximum limit allowed",
"The number of contracts exceeds minimum limit allowed": "The number of contracts exceeds minimum limit allowed",
"Order placement failed as your position may exceed the max": "Order placement failed as your position may exceed the max",
}
for key, msg in known_errors.items():
if key in error_text:
@@ -356,5 +375,5 @@ async def open_positions(
return "InvalidRequestError"
except Exception as e:
logger.error("Error opening position for user %s: %s", tg_id, e)
logger.error("Error opening position for user %s: %s", tg_id, e, exc_info=True)
return None

View File

@@ -25,11 +25,13 @@ async def set_tp_sl_for_position(
"""
try:
client = await get_bybit_client(tg_id)
take_profit = round(take_profit_price, 6) if take_profit_price is not None else None
stop_loss = round(stop_loss_price, 6) if stop_loss_price is not None else None
resp = client.set_trading_stop(
category="linear",
symbol=symbol,
takeProfit=str(round(take_profit_price, 5)),
stopLoss=str(round(stop_loss_price, 5)),
takeProfit=str(take_profit) if take_profit is not None else None,
stopLoss=str(stop_loss) if stop_loss is not None else None,
positionIdx=position_idx,
tpslMode="Full",
)

View File

@@ -1,10 +1,14 @@
import logging.config
import math
# import json
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_instruments_info import get_instruments_info
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
from app.bybit.open_positions import trading_cycle
from app.helper_functions import format_value, safe_float
from app.bybit.open_positions import trading_cycle, trading_cycle_profit
from app.bybit.set_functions.set_tp_sl import set_tp_sl_for_position
from app.helper_functions import format_value, safe_float, truncate_float
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("telegram_message_handler")
@@ -14,186 +18,328 @@ class TelegramMessageHandler:
def __init__(self, telegram_bot):
self.telegram_bot = telegram_bot
async def format_position_update(self, message):
pass
async def format_order_update(self, message, tg_id):
try:
order_data = message.get("data", [{}])[0]
symbol = format_value(order_data.get("symbol"))
qty = format_value(order_data.get("qty"))
side = format_value(order_data.get("side"))
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
order_status = format_value(order_data.get("orderStatus"))
price = format_value(order_data.get("price"))
trigger_price = format_value(order_data.get("triggerPrice"))
take_profit = format_value(order_data.get("takeProfit"))
stop_loss = format_value(order_data.get("stopLoss"))
user_additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
trigger_price = safe_float(user_additional_data.trigger_price)
if trigger_price > 0:
order_data = message.get("data", [{}])[0]
symbol = format_value(order_data.get("symbol"))
side = format_value(order_data.get("side"))
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
order_status = format_value(order_data.get("orderStatus"))
tr_price = format_value(order_data.get("triggerPrice"))
status_map = {
"Untriggered": "Условный ордер выставлен",
}
status_map = {
"Untriggered": "Условный ордер выставлен",
}
if order_status == "Filled" or order_status not in status_map:
return None
if order_status == "Filled" or order_status not in status_map:
return None
text = (
f"Торговая пара: {symbol}\n"
f"Количество: {qty}\n"
f"Движение: {side_rus}\n"
)
if price and price != "0":
text += f"Цена: {price}\n"
if take_profit and take_profit != "Нет данных":
text += f"Тейк-профит: {take_profit}\n"
if stop_loss and stop_loss != "Нет данных":
text += f"Стоп-лосс: {stop_loss}\n"
if trigger_price and trigger_price != "Нет данных":
text += f"Триггер цена: {trigger_price}\n"
text = (
f"Торговая пара: {symbol}\n"
f"Движение: {side_rus}\n"
)
if tr_price and tr_price != "Нет данных":
text += f"Триггер цена: {tr_price}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
await rq.set_trigger_price(tg_id=tg_id, trigger_price=0)
except Exception as e:
logger.error("Error in format_order_update: %s", e)
async def format_execution_update(self, message, tg_id):
try:
# logger.info("Execution update: %s", json.dumps(message))
execution = message.get("data", [{}])[0]
closed_size = format_value(execution.get("closedSize"))
symbol = format_value(execution.get("symbol"))
exec_price = format_value(execution.get("execPrice"))
exec_fee = format_value(execution.get("execFee"))
side = format_value(execution.get("side"))
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
exec_type = format_value(execution.get("execType"))
if exec_type == "Trade" or exec_type == "BustTrade":
closed_size = format_value(execution.get("closedSize"))
symbol = format_value(execution.get("symbol"))
exec_price = format_value(execution.get("execPrice"))
exec_qty = format_value(execution.get("execQty"))
exec_fees = format_value(execution.get("execFee"))
fee_rate = format_value(execution.get("feeRate"))
side = format_value(execution.get("side"))
if safe_float(closed_size) == 0:
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee)
user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
get_total_fee = user_auto_trading.total_fee
total_fee = safe_float(exec_fee) + safe_float(get_total_fee)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
)
if user_auto_trading is not None and user_auto_trading.fee is not None:
fee = user_auto_trading.fee
else:
fee = 0
exec_pnl = format_value(execution.get("execPnl"))
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee
if commission_fee == "Yes_commission_fee":
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
else:
total_pnl = safe_float(exec_pnl)
header = (
"Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
)
text = f"{header}\n" f"Торговая пара: {symbol}\n"
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
exec_bet = user_deals_data.order_quantity
base_quantity = user_deals_data.base_quantity
text += (
f"Цена исполнения: {exec_price}\n"
f"Текущая ставка: {exec_bet}\n"
f"Движение: {side_rus}\n"
f"Комиссия за сделку: {exec_fee}\n"
)
if safe_float(closed_size) > 0:
text += f"\nРеализованная прибыль: {total_pnl:.7f}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
user_symbols = user_auto_trading.symbol if user_auto_trading else None
if (
auto_trading
and safe_float(closed_size) > 0
and user_symbols is not None
):
if safe_float(total_pnl) > 0:
profit_text = "📈 Прибыль достигнута\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await rq.set_order_quantity(
tg_id=tg_id, order_quantity=base_quantity
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
if safe_float(exec_fees) == 0:
exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float(
fee_rate
)
else:
open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=open_order_text
)
res = await trading_cycle(
tg_id=tg_id, symbol=symbol, reverse_side=side
exec_fee = safe_float(exec_fees)
if safe_float(closed_size) == 0:
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee)
)
if res == "OK":
pass
get_total_fee = 0
if user_auto_trading is not None:
get_total_fee = user_auto_trading.total_fee
total_fee = safe_float(exec_fee) + safe_float(get_total_fee)
exec_pnl = format_value(execution.get("execPnl"))
ex_pnl = safe_float(exec_pnl)
pnl = safe_float(exec_pnl)
header = (
"Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
)
text = f"{header}\n" f"Торговая пара: {symbol}\n"
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
if user_deals_data is None:
commission_fee = "Yes_commission_fee"
commission_place = "Commission_for_qty"
else:
commission_fee = user_deals_data.commission_fee or "Yes_commission_fee"
commission_place = user_deals_data.commission_place or "Commission_for_qty"
current_series = user_deals_data.current_series
current_step = user_deals_data.current_step
order_quantity = user_deals_data.order_quantity
pnl_series = user_deals_data.pnl_series
margin_type = user_deals_data.margin_type
take_profit_percent = user_deals_data.take_profit_percent
stop_loss_percent = user_deals_data.stop_loss_percent
fee = safe_float(user_auto_trading.fee)
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
leverage = safe_float(user_deals_data.leverage)
if commission_fee == "Yes_commission_fee":
if commission_place == "Commission_for_qty":
total_quantity = safe_float(order_quantity) + safe_float(
total_fee
) * 2
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Количество контрактов превышает допустимое максимальное количество контрактов",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
total_quantity = safe_float(order_quantity)
else:
total_quantity = safe_float(order_quantity)
if user_deals_data is not None and auto_trading and safe_float(closed_size) == 0:
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
)
text += f"Текущая ставка: {total_quantity:.2f} USDT\n"
text += f"Серия №: {current_series}\n"
text += f"Сделка №: {current_step}\n"
text += (
f"Цена исполнения: {exec_price}\n"
f"Комиссия: {exec_fee:.8f}\n"
)
if safe_float(closed_size) == 0:
instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep")
qty_step = safe_float(qty_step_str)
qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(exec_price)
decimals = abs(int(round(math.log10(qty_step))))
qty_format = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_format, decimals)
total_commission = 0
if commission_fee == "Yes_commission_fee":
if commission_place == "Commission_for_tp":
total_commission = safe_float(total_fee) / qty_formatted
if margin_type == "ISOLATED_MARGIN":
if side == "Buy":
take_profit_price = safe_float(exec_price) * (
1 + take_profit_percent / 100) + total_commission
stop_loss_price = None
else:
take_profit_price = safe_float(exec_price) * (
1 - take_profit_percent / 100) - total_commission
stop_loss_price = None
else:
if side == "Buy":
take_profit_price = safe_float(exec_price) * (
1 + take_profit_percent / 100) + total_commission
stop_loss_price = safe_float(exec_price) * (1 - stop_loss_percent / 100)
else:
take_profit_price = safe_float(exec_price) * (
1 - take_profit_percent / 100) - total_commission
stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100)
take_profit_price = max(take_profit_price, 0)
stop_loss_price = max(stop_loss_price, 0)
ress = await set_tp_sl_for_position(tg_id=tg_id,
symbol=symbol,
take_profit_price=take_profit_price,
stop_loss_price=stop_loss_price,
position_idx=0)
if ress:
take_profit_truncated = await truncate_float(take_profit_price, 6)
text += (f"Движение: {side_rus}\n"
f"Тейк-профит: {take_profit_truncated}\n"
)
if stop_loss_price is not None:
stop_loss_truncated = await truncate_float(stop_loss_price, 6)
else:
stop_loss_truncated = None
if stop_loss_truncated is not None:
text += f"Стоп-лосс: {stop_loss_truncated}\n"
else:
deals = await get_active_positions_by_symbol(
tg_id=tg_id, symbol=symbol
)
position = next((d for d in deals if d.get("symbol") == symbol), None)
if position:
liq_price = position.get("liqPrice", 0)
text += f"Цена ликвидации: {liq_price}\n"
else:
text += (f"Движение: {side_rus}\n"
"Не удалось установить ТП и СЛ\n")
else:
if auto_trading:
new_pnl = safe_float(pnl_series) + total_pnl
await rq.set_pnl_series_by_symbol(
tg_id=tg_id, symbol=symbol, pnl_series=new_pnl)
text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n"
text += f"Реализованная прибыль: {total_pnl:.4f}\n"
text += f"Прибыль серии: {safe_float(new_pnl):.4f}\n"
else:
text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n"
text += f"Реализованная прибыль: {total_pnl:.4f}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
user_symbols = user_auto_trading.symbol if user_auto_trading else None
if (
auto_trading
and safe_float(closed_size) > 0
and user_symbols is not None
):
if safe_float(pnl) > 0:
profit_text = "📈 Начинаю новую серию с базовой ставки\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
)
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
await rq.set_last_side_by_symbol(
tg_id=tg_id, symbol=symbol, last_side=r_side)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await rq.set_pnl_series_by_symbol(tg_id=tg_id, symbol=symbol, pnl_series=0)
res = await trading_cycle_profit(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
pass
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки",
"Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
)
else:
open_order_text = "\n❗️ Открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
chat_id=tg_id, text=open_order_text
)
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
res = await trading_cycle(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
pass
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки",
"Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
)
except Exception as e:
logger.error("Error in telegram_message_handler: %s", e)
logger.error("Error in telegram_message_handler: %s", e, exc_info=True)

View File

@@ -58,7 +58,8 @@ class WebSocketBot:
)
logger.info("User %s connected to WebSocket", tg_id)
else:
await asyncio.sleep(30)
await asyncio.sleep(5)
await self.try_connect_user(api_key, api_secret, tg_id)
await asyncio.sleep(10)
@@ -73,6 +74,7 @@ class WebSocketBot:
"""Try to connect a user to the WebSocket."""
try:
self.ws_private = WebSocket(
demo=True,
testnet=False,
channel_type="private",
api_key=api_key,
@@ -81,12 +83,6 @@ class WebSocketBot:
self.user_sockets[tg_id] = self.ws_private
# Connect to the WebSocket private channel
# Handle position updates
self.ws_private.position_stream(
lambda msg: self.loop.call_soon_threadsafe(
asyncio.create_task, self.handle_position_update(msg)
)
)
# Handle order updates
self.ws_private.order_stream(
lambda msg: self.loop.call_soon_threadsafe(
@@ -104,16 +100,12 @@ class WebSocketBot:
logger.error("Error connecting user %s: %s", tg_id, e)
return False
async def handle_position_update(self, message):
"""Handle position updates."""
await self.message_handler.format_position_update(message)
async def handle_order_update(self, message, tg_id):
"""Handle order updates."""
await self.message_handler.format_order_update(message, tg_id)
async def handle_execution_update(self, message, tg_id):
"""Handle execution updates."""
"""Handle execution updates without duplicate processing."""
await self.message_handler.format_execution_update(message, tg_id)
@staticmethod

View File

@@ -179,3 +179,9 @@ async def calculate_total_budget(
total += r_quantity
return total
async def truncate_float(f, decimals=4):
factor = 10 ** decimals
return int(f * factor) / factor

View File

@@ -121,9 +121,11 @@ async def set_symbol(message: Message, state: FSMContext) -> None:
)
await rq.set_leverage(tg_id=message.from_user.id, leverage=str(max_leverage))
risk_percent = 100 / safe_float(max_leverage)
risk_percent = 10 / safe_float(max_leverage)
await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent(
tg_id=message.from_user.id, take_profit_percent=risk_percent)
await rq.set_trigger_price(tg_id=message.from_user.id, trigger_price=0)
await rq.set_order_quantity(tg_id=message.from_user.id, order_quantity=1.0)

View File

@@ -4,9 +4,6 @@ from aiogram import Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
import database.request as rq
from app.bybit.close_positions import cancel_order, close_position
from app.helper_functions import safe_float
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
@@ -28,31 +25,6 @@ async def close_position_handler(
:return: None
"""
try:
data = callback_query.data
parts = data.split("_")
symbol = parts[2]
side = parts[3]
position_idx = int(parts[4])
qty = safe_float(parts[5])
await rq.set_auto_trading(
tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
side=side,
)
res = await close_position(
tg_id=callback_query.from_user.id,
symbol=symbol,
side=side,
position_idx=position_idx,
qty=qty,
)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии позиции.")
return
await callback_query.answer(text="Позиция успешно закрыта.")
logger.debug(
"Command close_position processed successfully for user: %s",
callback_query.from_user.id,
@@ -81,19 +53,6 @@ async def cancel_order_handler(
:return: None
"""
try:
data = callback_query.data
parts = data.split("_")
symbol = parts[2]
order_id = parts[3]
res = await cancel_order(
tg_id=callback_query.from_user.id, symbol=symbol, order_id=order_id
)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии ордера.")
return
await callback_query.answer(text="Ордер успешно закрыт.")
logger.debug(
"Command close_order processed successfully for user: %s",
callback_query.from_user.id,

View File

@@ -7,6 +7,7 @@ from aiogram.types import CallbackQuery, Message
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_instruments_info import get_instruments_info
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
from app.bybit.set_functions.set_leverage import set_leverage
from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.helper_functions import is_int, is_number, safe_float
@@ -42,7 +43,7 @@ async def settings_for_trade_mode(
text="Выберите режим торговли:\n\n"
"Лонг - все сделки серии открываются на покупку.\n"
"Шорт - все сделки серии открываются на продажу.\n"
"Свитч - направление каждой сделки серии меняется по переменно.\n",
"Свитч - направление каждой сделки в рамках серии меняется попеременно.\n",
reply_markup=kbi.trade_mode,
)
logger.debug(
@@ -104,10 +105,10 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None:
await state.clear()
@router_additional_settings.callback_query(F.data == "switch_side_start")
async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None:
@router_additional_settings.callback_query(F.data == "switch_side_second")
async def switch_side_second(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'switch_side_start' callback query.
Handles the 'switch_side_second' callback query.
Clears the current FSM state, edits the message text to display the switch side start message,
and shows an inline keyboard for selection.
@@ -122,13 +123,13 @@ async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) ->
try:
await state.clear()
await callback_query.message.edit_text(
text="Выберите направление первой сделки серии:\n\n"
text="Выберите направление первой сделки последующих серии:\n\n"
"По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
"Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
reply_markup=kbi.switch_side,
)
logger.debug(
"Command switch_side_start processed successfully for user: %s",
"Command switch_side_second processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
@@ -136,7 +137,7 @@ async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) ->
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command switch_side_start for user %s: %s",
"Error processing command switch_side_second for user %s: %s",
callback_query.from_user.id,
e,
)
@@ -192,6 +193,89 @@ async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext)
await state.clear()
@router_additional_settings.callback_query(F.data == "switch_side_start")
async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'switch_side_start' callback query.
Clears the current FSM state, edits the message text to display the switch side second message,
and shows an inline keyboard for selection.
Args:
callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
state (FSMContext): Finite State Machine context for the current user session.
Logs:
Success or error messages with user identification.
"""
try:
await state.clear()
await callback_query.message.edit_text(
text="Выберите направление первой сделки:\n\n", reply_markup=kbi.side_for_switch
)
logger.debug(
"Command switch_side_start processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command switch_side_start for user %s: %s",
callback_query.from_user.id,
e,
)
@router_additional_settings.callback_query(lambda c: c.data == "buy_switch" or c.data == "sell_switch")
async def switch_side_handler_2(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles callback queries related to switch side selection.
Updates FSM context with selected switch side and persists the choice in database.
Sends an acknowledgement to user and clears FSM state afterward.
Args:
callback_query (CallbackQuery): Incoming callback query indicating selected switch side.
state (FSMContext): Finite State Machine context for the current user session.
Logs:
Success or error messages with user identification.
"""
try:
if callback_query.data == "sell_switch":
side = "Sell"
side_rus = "Продажа"
else:
side = "Buy"
side_rus = "Покупка"
req = await rq.set_side(
tg_id=callback_query.from_user.id, side=side
)
if not req:
await callback_query.answer(
text="Произошла ошибка при смене направления. Пожалуйста, попробуйте позже."
)
return
await callback_query.answer(text=f"Выбрано: {side_rus}")
logger.debug(
"Switch side changed successfully for user: %s", callback_query.from_user.id
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при смене направления.")
logger.error(
"Error processing set switch_side for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()
@router_additional_settings.callback_query(F.data == "margin_type")
async def settings_for_margin_type(
callback_query: CallbackQuery, state: FSMContext
@@ -211,6 +295,31 @@ async def settings_for_margin_type(
"""
try:
await state.clear()
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)
else:
size = 0
if safe_float(size) > 0:
await callback_query.answer(
text="У вас есть активная позиция по текущей паре",
)
return
orders = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
if orders is not None:
await callback_query.answer(
text="У вас есть активный ордер по текущей паре",
)
return
await callback_query.message.edit_text(
text="Выберите тип маржи:\n\n"
"Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям",
@@ -551,9 +660,11 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
text=f"Кредитное плечо успешно установлено на {leverage_float}",
reply_markup=kbi.back_to_additional_settings,
)
risk_percent = 100 / safe_float(leverage_float)
risk_percent = 10 / safe_float(leverage_float)
await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent(
tg_id=message.from_user.id, take_profit_percent=risk_percent)
logger.info(
"User %s set leverage: %s", message.from_user.id, leverage_float
)

View File

@@ -98,7 +98,7 @@ async def set_take_profit_percent(message: Message, state: FSMContext) -> None:
)
return
if safe_float(take_profit_percent_value) < 1 or safe_float(take_profit_percent_value) > 100:
if safe_float(take_profit_percent_value) < 0.1 or safe_float(take_profit_percent_value) > 100:
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
@@ -219,7 +219,7 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None:
)
return
if safe_float(stop_loss_percent_value) < 1 or safe_float(stop_loss_percent_value) > 100:
if safe_float(stop_loss_percent_value) < 0.1 or safe_float(stop_loss_percent_value) > 100:
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
@@ -341,3 +341,83 @@ async def set_commission_fee(callback_query: CallbackQuery, state: FSMContext) -
)
finally:
await state.clear()
@router_risk_management.callback_query(F.data == "compensation_commission")
async def compensation_commission(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'compensation_commission' callback query.
Clears the current FSM state, edits the message text to display the compensation commission options,
and shows an inline keyboard for selection.
Args:
callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
state (FSMContext): Finite State Machine context for the current user session.
Logs:
Success or error messages with user identification.
"""
try:
await state.clear()
msg = await callback_query.message.edit_text(
text="Выберите за счет чего будет происходить компенсация комиссии: ",
reply_markup=kbi.commission_place,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command compensation_commission processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command compensation_commission for user %s: %s",
callback_query.from_user.id,
e,
)
@router_risk_management.callback_query(
lambda c: c.data in ["Commission_for_qty", "Commission_for_tp"]
)
async def set_compensation_commission(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles user input for setting the compensation commission.
Updates FSM context with the selected option and persists the choice in database.
Sends an acknowledgement to user and clears FSM state afterward.
Args:
callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
state (FSMContext): Finite State Machine context for the current user session.
Logs:
Success or error messages with user identification.
"""
try:
req = await rq.set_commission_place(
tg_id=callback_query.from_user.id, commission_place=callback_query.data
)
if not req:
await callback_query.answer(
text="Произошла ошибка при установке компенсации комиссии. Пожалуйста, попробуйте позже."
)
return
if callback_query.data == "Commission_for_qty":
await callback_query.answer(text="Комиссия компенсируется по ставке.")
else:
await callback_query.answer(text="Комиссия компенсируется по тейк-профиту.")
except Exception as e:
logger.error(
"Error processing command compensation_commission for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()

View File

@@ -62,10 +62,19 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
max_bets = additional_data.max_bets_in_series
quantity = f(additional_data.order_quantity)
trigger_price = f(additional_data.trigger_price) or 0
side = additional_data.side
side_map = {
"Buy": "Лонг",
"Sell": "Шорт",
}
side_rus = side_map.get(side, side)
switch_side_mode = ""
side = ""
if trade_mode == "Switch":
switch_side_mode = f"- Направление первой сделки: {switch_side}\n"
side = f"- Направление первой сделки: {side_rus}\n"
switch_side_mode = f"- Направление первой сделки последующих серии: {switch_side}\n"
total_budget = await calculate_total_budget(
quantity=quantity,
@@ -75,6 +84,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
text = (
f"Основные настройки:\n\n"
f"- Режим торговли: {trade_mode_rus}\n"
f"{side}"
f"{switch_side_mode}"
f"- Тип маржи: {margin_type_rus}\n"
f"- Размер кредитного плеча: {leverage:.2f}\n"
@@ -120,12 +130,17 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N
commission_fee_rus = (
"Да" if commission_fee == "Yes_commission_fee" else "Нет"
)
commission_place = risk_management_data.commission_place
commission_place_rus = (
"Ставке" if commission_place == "Commission_for_qty" else "Тейк-профиту"
)
await callback_query.message.edit_text(
text=f"Риск-менеджмент:\n\n"
f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n"
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n"
f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n",
f"- Процент изменения цены для фиксации прибыли: {take_profit_percent:.2f}%\n"
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent:.2f}%\n\n"
f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n"
f"- Компенсация комиссии по: {commission_place_rus}",
reply_markup=kbi.risk_management,
)
logger.debug(
@@ -164,7 +179,7 @@ async def conditions(callback_query: CallbackQuery, state: FSMContext) -> None:
start_timer = conditional_settings_data.timer_start or 0
await callback_query.message.edit_text(
text="Условия торговли:\n\n"
f"- Таймер для старта: {start_timer} мин.\n",
f"- Таймер для старта: {start_timer} мин.\n",
reply_markup=kbi.conditions,
)
logger.debug(

View File

@@ -7,7 +7,7 @@ from aiogram.types import CallbackQuery
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
from app.bybit.open_positions import start_trading_cycle
from app.helper_functions import safe_float
from app.telegram.tasks.tasks import (
@@ -33,6 +33,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
"""
try:
await state.clear()
tg_id = callback_query.from_user.id
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
@@ -46,7 +47,16 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
if safe_float(size) > 0:
await callback_query.answer(
text="У вас есть активная позиция",
text="У вас есть активная позиция по текущей паре",
)
return
orders = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
if orders is not None:
await callback_query.answer(
text="У вас есть активный ордер по текущей паре",
)
return
@@ -73,22 +83,33 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
symbol=symbol,
auto_trading=True,
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
res = await start_trading_cycle(
tg_id=callback_query.from_user.id,
)
error_messages = {
"Limit price is out min price": "Цена лимитного ордера меньше минимального",
"Limit price is out max price": "Цена лимитного ордера больше максимального",
"Limit price is out min price": "Цена лимитного ордера меньше допустимого",
"Limit price is out max price": "Цена лимитного ордера больше допустимого",
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"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": "️️Количество контрактов превышает допустимое максимальное количество контрактов",
"The number of contracts exceeds minimum limit allowed": "Количество контрактов превышает допустимое минимальное количество контрактов",
"Order does not meet minimum order value": "Сумма ставки меньше допустимого для запуска торговли. "
"Увеличьте ставку, чтобы запустить торговлю",
"position idx not match position mode": "Измените режим позиции, чтобы запустить торговлю",
"Qty invalid": "Некорректное значение ставки для данного инструмента",
"The number of contracts exceeds maximum limit allowed": "Превышен максимальный лимит ставки",
"The number of contracts exceeds minimum limit allowed": "️️Лимит ставки меньше минимально допустимого",
"Order placement failed as your position may exceed the max":
"Не удалось разместить ордер, так как ваша позиция может превышать максимальный лимит."
"Пожалуйста, уменьшите кредитное плечо, чтобы увеличить максимальное значение"
}
if res == "OK":
@@ -112,7 +133,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error(
"Error processing command long for user %s: %s",
"Error processing command start_trading for user %s: %s",
callback_query.from_user.id,
e,
)
@@ -124,7 +145,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
lambda c: c.data == "cancel_timer_merged"
)
async def cancel_start_trading(
callback_query: CallbackQuery, state: FSMContext
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Handles the "cancel_timer" callback query.

View File

@@ -5,6 +5,7 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
from app.bybit.close_positions import close_position_by_symbol
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.telegram.tasks.tasks import add_stop_task, cancel_stop_task
@@ -27,6 +28,7 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
tg_id=callback_query.from_user.id
)
timer_end = conditional_data.timer_end
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
async def delay_start():
if timer_end > 0:
@@ -37,30 +39,15 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
await asyncio.sleep(timer_end * 60)
user_auto_trading_list = await rq.get_all_user_auto_trading(
tg_id=callback_query.from_user.id
await rq.set_auto_trading(
tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
)
await close_position_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)
if any(item.auto_trading for item in user_auto_trading_list):
for active_auto_trading in user_auto_trading_list:
if active_auto_trading.auto_trading:
symbol = active_auto_trading.symbol
req = await rq.set_auto_trading(
tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
)
if not req:
await callback_query.message.edit_text(
text="Произошла ошибка при остановке торговли",
reply_markup=kbi.profile_bybit,
)
return
await callback_query.message.edit_text(
text="Торговля остановлена", reply_markup=kbi.profile_bybit
)
else:
await callback_query.message.edit_text(text="Нет активной торговли")
task = asyncio.create_task(delay_start())
await add_stop_task(user_id=callback_query.from_user.id, task=task)

View File

@@ -36,6 +36,7 @@ main_menu = InlineKeyboardMarkup(
)
],
[InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")],
[InlineKeyboardButton(text="Остановить торговлю", callback_data="stop_trading")],
]
)
@@ -93,7 +94,10 @@ def get_additional_settings_keyboard(mode: str
if mode == "Switch":
buttons.append(
[InlineKeyboardButton(text="Направление первой сделки", callback_data="switch_side_start")]
[InlineKeyboardButton(text="Направление первой сделки первой серии", callback_data="switch_side_start")]
)
buttons.append(
[InlineKeyboardButton(text="Направление первой сделки последующих серии", callback_data="switch_side_second")]
)
buttons.append(
@@ -147,6 +151,19 @@ switch_side = InlineKeyboardMarkup(
]
)
side_for_switch = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text="Лонг", callback_data="buy_switch"),
InlineKeyboardButton(text="Шорт", callback_data="sell_switch"),
],
[
InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
],
]
)
margin_type = InlineKeyboardMarkup(
inline_keyboard=[
[
@@ -193,6 +210,7 @@ risk_management = InlineKeyboardMarkup(
),
],
[InlineKeyboardButton(text="Комиссия биржи", callback_data="commission_fee")],
[InlineKeyboardButton(text="Компенсация комиссии", callback_data="compensation_commission")],
[
InlineKeyboardButton(text="Назад", callback_data="main_settings"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
@@ -222,6 +240,20 @@ commission_fee = InlineKeyboardMarkup(
]
)
commission_place = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text="По ставке", callback_data="Commission_for_qty"),
InlineKeyboardButton(text="По тейк-профиту", callback_data="Commission_for_tp"),
],
[
InlineKeyboardButton(text="Назад", callback_data="risk_management"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
],
]
)
# conditions
conditions = InlineKeyboardMarkup(
inline_keyboard=[

View File

@@ -92,6 +92,7 @@ class UserAdditionalSettings(Base):
nullable=False, unique=True)
trade_mode = Column(String, nullable=False, default="Merged_Single")
switch_side = Column(String, nullable=False, default="По направлению")
side = Column(String, nullable=False, default="Buy")
trigger_price = Column(Float, nullable=False, default=0.0)
margin_type = Column(String, nullable=False, default="ISOLATED_MARGIN")
leverage = Column(String, nullable=False, default="10")
@@ -113,6 +114,7 @@ class UserRiskManagement(Base):
take_profit_percent = Column(Float, nullable=False, default=1)
stop_loss_percent = Column(Float, nullable=False, default=1)
commission_fee = Column(String, nullable=False, default="Yes_commission_fee")
commission_place = Column(String, nullable=False, default="Commission_for_qty")
user = relationship("User", back_populates="user_risk_management")
@@ -143,6 +145,7 @@ class UserDeals(Base):
current_step = Column(Integer, nullable=True)
symbol = Column(String, nullable=True)
trade_mode = Column(String, nullable=True)
side_mode = Column(String, nullable=True)
base_quantity = Column(Float, nullable=True)
margin_type = Column(String, nullable=True)
leverage = Column(String, nullable=True)
@@ -154,6 +157,12 @@ class UserDeals(Base):
take_profit_percent = Column(Integer, nullable=True)
stop_loss_percent = Column(Integer, nullable=True)
trigger_price = Column(Float, nullable=True)
current_series = Column(Integer, nullable=True)
commission_fee = Column(String, nullable=True)
commission_place = Column(String, nullable=True)
pnl_series = Column(Float, nullable=True)
take_profit = Column(Float, nullable=False, default=0.0)
stop_loss = Column(Float, nullable=False, default=0.0)
user = relationship("User", back_populates="user_deals")

View File

@@ -200,6 +200,8 @@ async def create_user_additional_settings(tg_id: int) -> None:
user=user,
trade_mode="Long", # Default value
switch_side="По направлению",
side="Buy",
trigger_price=0.0,
margin_type="ISOLATED_MARGIN",
leverage="10",
order_quantity=1.0,
@@ -358,6 +360,45 @@ async def set_switch_side(tg_id: int, switch_side: str) -> bool:
return False
async def set_side(tg_id: int, side: str) -> bool:
"""
Set side for a user in the database.
:param tg_id: Telegram user ID
:param side: "BUY" or "SELL"
:return: True if successful, False otherwise
"""
try:
async with async_session() as session:
result = await session.execute(
select(User)
.options(joinedload(User.user_additional_settings))
.filter_by(tg_id=tg_id)
)
user = result.scalars().first()
if user:
if user.user_additional_settings:
# Updating existing record
user.user_additional_settings.side = side
else:
# Creating new record
user_additional_settings = UserAdditionalSettings(
side=side,
user=user,
)
session.add(user_additional_settings)
await session.commit()
logger.info("User side updated for user: %s", tg_id)
return True
else:
logger.error("User not found with tg_id: %s", tg_id)
return False
except Exception as e:
logger.error("Error adding/updating user side for user %s: %s", tg_id, e)
return False
async def set_leverage(tg_id: int, leverage: str) -> bool:
"""
Set leverage for a user in the database.
@@ -590,6 +631,7 @@ async def create_user_risk_management(tg_id: int) -> None:
take_profit_percent=1.0,
stop_loss_percent=1.0,
commission_fee="Yes_commission_fee",
commission_place="Commission_for_qty"
)
session.add(user_risk_management)
await session.commit()
@@ -749,6 +791,47 @@ async def set_commission_fee(tg_id: int, commission_fee: str) -> bool:
return False
async def set_commission_place(tg_id: int, commission_place: str) -> bool:
"""
Set commission place for a user in the database.
:param tg_id: Telegram user ID
:param commission_place: Commission place
:return: True if successful, False otherwise
"""
try:
async with async_session() as session:
result = await session.execute(
select(User)
.options(joinedload(User.user_risk_management))
.filter_by(tg_id=tg_id)
)
user = result.scalars().first()
if user:
if user.user_risk_management:
# Updating existing record
user.user_risk_management.commission_place = commission_place
else:
# Creating new record
user_risk_management = UserRiskManagement(
commission_place=commission_place,
user=user,
)
session.add(user_risk_management)
await session.commit()
logger.info("User commission place updated for user: %s", tg_id)
return True
else:
logger.error("User not found with tg_id: %s", tg_id)
return False
except Exception as e:
logger.error(
"Error adding/updating user commission place for user %s: %s", tg_id, e
)
return False
# USER CONDITIONAL SETTINGS
@@ -895,9 +978,10 @@ async def set_stop_timer(tg_id: int, timer_end: int) -> bool:
async def set_user_deal(
tg_id: int,
symbol: str,
last_side: str,
current_step: int,
current_series: int,
trade_mode: str,
side_mode: str,
margin_type: str,
leverage: str,
order_quantity: float,
@@ -906,15 +990,19 @@ async def set_user_deal(
max_bets_in_series: int,
take_profit_percent: int,
stop_loss_percent: int,
base_quantity: float
base_quantity: float,
commission_fee: str,
commission_place: str,
pnl_series: float
):
"""
Set the user deal in the database.
:param tg_id: Telegram user ID
:param symbol: Symbol
:param last_side: Last side
:param current_step: Current step
:param current_series: Current series
:param trade_mode: Trade mode
:param side_mode: Side mode
:param margin_type: Margin type
:param leverage: Leverage
:param order_quantity: Order quantity
@@ -924,6 +1012,9 @@ async def set_user_deal(
:param take_profit_percent: Take profit percent
:param stop_loss_percent: Stop loss percent
:param base_quantity: Base quantity
:param commission_fee: Commission fee
:param commission_place: Commission place
:param pnl_series: PNL series
:return: bool
"""
try:
@@ -941,9 +1032,10 @@ async def set_user_deal(
if deal:
# Updating existing record
deal.last_side = last_side
deal.current_step = current_step
deal.current_series = current_series
deal.trade_mode = trade_mode
deal.side_mode = side_mode
deal.margin_type = margin_type
deal.leverage = leverage
deal.order_quantity = order_quantity
@@ -953,14 +1045,18 @@ async def set_user_deal(
deal.take_profit_percent = take_profit_percent
deal.stop_loss_percent = stop_loss_percent
deal.base_quantity = base_quantity
deal.commission_fee = commission_fee
deal.commission_place = commission_place
deal.pnl_series = pnl_series
else:
# Creating new record
new_deal = UserDeals(
user=user,
symbol=symbol,
last_side=last_side,
current_step=current_step,
current_series=current_series,
trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=order_quantity,
@@ -969,7 +1065,10 @@ async def set_user_deal(
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity
base_quantity=base_quantity,
commission_fee=commission_fee,
commission_place=commission_place,
pnl_series=pnl_series
)
session.add(new_deal)
@@ -1050,6 +1149,119 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float):
return False
async def set_last_side_by_symbol(tg_id: int, symbol: str, last_side: str):
"""Set last side for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.last_side = last_side
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set last side for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal last side for user %s and symbol %s: %s", tg_id, symbol, e)
return False
async def set_current_series(tg_id: int, symbol: str, current_series: int):
"""Set current series for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.current_series = current_series
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set current series for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal current series for user %s and symbol %s: %s", tg_id, symbol, e)
return False
async def set_tp_sl_by_symbol(tg_id: int, symbol: str, tp: float, sl: float):
"""Set tp and sl for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.take_profit = tp
record.stop_loss = sl
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set tp and sl for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal tp and sl for user %s and symbol %s: %s", tg_id, symbol, e)
return False
async def set_pnl_series_by_symbol(tg_id: int, symbol: str, pnl_series: float):
"""Set pnl series for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.pnl_series = pnl_series
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set pnl series for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal pnl series for user %s and symbol %s: %s", tg_id, symbol, e)
return False
# USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int):

1
run.py
View File

@@ -46,7 +46,6 @@ async def main():
with contextlib.suppress(asyncio.CancelledError):
await ws_task
await tg_task
await web_socket.clear_user_sockets()
except Exception as e:
logger.error("Bot stopped with error: %s", e)