From 507b0829487b2ef2b45d691e4eedfd4befbcb114 Mon Sep 17 00:00:00 2001 From: Vitaliy Klychkov Date: Wed, 28 Jun 2023 23:09:34 +0000 Subject: [PATCH] Add position to signal --- ...3-06-28_update_deals_table_add_position.py | 28 +++++++++++++++++++ src/app/models/deal.py | 1 + src/app/repositories/deal.py | 16 +++++++---- src/app/schemas/signal.py | 4 +++ src/app/services/deal.py | 10 +++---- src/bot/exchange/bot.py | 7 +++-- src/bot/exchange/strategies/base_strategy.py | 18 ++++++------ src/bot/exchange/strategies/grid_strategy.py | 9 ++---- src/bot/exchange/strategy_db_helper.py | 7 +++-- src/bot/signal_dispatcher.py | 19 ++++++++++--- 10 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 migrations/versions/2023-06-28_update_deals_table_add_position.py diff --git a/migrations/versions/2023-06-28_update_deals_table_add_position.py b/migrations/versions/2023-06-28_update_deals_table_add_position.py new file mode 100644 index 0000000..a9f3677 --- /dev/null +++ b/migrations/versions/2023-06-28_update_deals_table_add_position.py @@ -0,0 +1,28 @@ +"""update_deals_table_add_position + +Revision ID: 3d9358bcf686 +Revises: 900e1be8f4c6 +Create Date: 2023-06-28 21:50:47.237178 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3d9358bcf686' +down_revision = '900e1be8f4c6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('deals', sa.Column('position', sa.SmallInteger(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/src/app/models/deal.py b/src/app/models/deal.py index b337989..f4a4449 100755 --- a/src/app/models/deal.py +++ b/src/app/models/deal.py @@ -19,4 +19,5 @@ class Deal(Base): date_close: Mapped[datetime] = mapped_column(DateTime(), nullable=True, default=None) pnl: Mapped[decimal] = mapped_column(Numeric(), nullable=True) bot_id: Mapped[int] = mapped_column(Integer(), nullable=False) + position: Mapped[int] = mapped_column(Integer(), nullable=True) order: Mapped[List["Order"]] = relationship(cascade="all, delete", passive_deletes=True) diff --git a/src/app/repositories/deal.py b/src/app/repositories/deal.py index 2f848ff..d347958 100644 --- a/src/app/repositories/deal.py +++ b/src/app/repositories/deal.py @@ -1,6 +1,6 @@ import decimal from datetime import datetime -from typing import Optional +from typing import Optional, Union from sqlalchemy import select, and_, func @@ -8,13 +8,13 @@ from src.db.session import get_async_session -async def create_deal(bot_id: int, pair: str, date_open: datetime, pnl: Optional[decimal] = None): +async def create_deal(bot_id: int, pair: str, date_open: datetime, position: Union[int, None] = None): async with get_async_session() as session: deal = Deal( bot_id=bot_id, pair=pair, date_open=date_open, - pnl=pnl + position=position ) session.add(deal) await session.commit() @@ -35,13 +35,17 @@ async def update_deal(deal_id: int, **update_values) -> Deal: return deal -async def get_bot_last_deal(bot_id: int, pair: str) -> Deal: +async def get_bot_last_deal(bot_id: int, pair: str, position: Union[int, None] = None) -> Deal: async with get_async_session() as session: query = select(Deal).where(and_(Deal.bot_id == bot_id, Deal.pair == pair, Deal.date_close.is_(None))).order_by( Deal.id.desc()) - deals = await session.execute(query) - return deals.scalar() + if position is not None: + query = query.where(Deal.position == position) + + result = await session.execute(query) + + return result.scalar() async def get_deal_by_id(deal_id: int) -> Deal: diff --git a/src/app/schemas/signal.py b/src/app/schemas/signal.py index 4f4cca8..584ae27 100644 --- a/src/app/schemas/signal.py +++ b/src/app/schemas/signal.py @@ -7,12 +7,14 @@ class OpenSignal(BaseModel): type_of_signal: Literal['open'] pair: str amount: float + position: Optional[int] class CloseSignal(BaseModel): bot_id: int type_of_signal: Literal['close'] pair: str + position: Optional[int] amount: Optional[float] deal_id: Optional[int] @@ -22,3 +24,5 @@ class AddSignal(BaseModel): type_of_signal: Literal['add'] pair: str amount: float + deal_id: Optional[int] + position: Optional[int] diff --git a/src/app/services/deal.py b/src/app/services/deal.py index 7096cf3..aee40ea 100644 --- a/src/app/services/deal.py +++ b/src/app/services/deal.py @@ -1,21 +1,21 @@ from datetime import datetime -from typing import List +from typing import List, Union from src.app.models import Deal from src.app.repositories import deal as repository from src.bot.exceptions.not_found_exception import NotFoundException -async def create_deal(bot_id: int, pair: str, date_open: datetime) -> Deal: - return await repository.create_deal(bot_id=bot_id, pair=pair, date_open=date_open) +async def create_deal(bot_id: int, pair: str, date_open: datetime, position: Union[int, None] = None) -> Deal: + return await repository.create_deal(bot_id=bot_id, pair=pair, date_open=date_open, position=position) async def increment_safety_orders_count(deal_id: int) -> int: return await repository.increment_safety_orders_count(deal_id=deal_id) -async def get_deal(bot_id: int, pair: str) -> Deal: - deal = await repository.get_bot_last_deal(bot_id=bot_id, pair=pair) +async def get_deal(bot_id: int, pair: str, position: Union[int, None] = None) -> Deal: + deal = await repository.get_bot_last_deal(bot_id=bot_id, pair=pair, position=position) if not deal: raise NotFoundException( diff --git a/src/bot/exchange/bot.py b/src/bot/exchange/bot.py index 941bb6d..8137e68 100644 --- a/src/bot/exchange/bot.py +++ b/src/bot/exchange/bot.py @@ -1,3 +1,5 @@ +from typing import Union + from src.app.repositories.bot import get_bot from src.app.services.bot import get_copy_bots from src.app.services.deal import is_deal_exist @@ -15,12 +17,13 @@ class Bot: - def __init__(self, bot_id: int, pair: str, type_of_signal: str) -> None: + def __init__(self, bot_id: int, pair: str, type_of_signal: str, position: Union[int, None] = None) -> None: self.margin_type = None self.exchange = None self.type_of_signal = type_of_signal self.bot_id = bot_id self.pair = pair + self.position = position async def get_copy_bots(self): if self.bot_id in range(100, 300): @@ -96,7 +99,7 @@ def get_strategy(self, side: BaseSide): if self.bot_id == 3 or self.bot_id == 1 or self.bot_id == 2: return SimpleStrategy(bot_id=self.bot_id, side=side, pair=self.pair) - return GridStrategy(bot_id=self.bot_id, side=side, pair=self.pair) + return GridStrategy(bot_id=self.bot_id, side=side, pair=self.pair, position=self.position) def get_bot_name(self): return f'Bot id: {self.bot_id} ({self.exchange.get_exchange_name()})' diff --git a/src/bot/exchange/strategies/base_strategy.py b/src/bot/exchange/strategies/base_strategy.py index 50bc5bb..ee40577 100644 --- a/src/bot/exchange/strategies/base_strategy.py +++ b/src/bot/exchange/strategies/base_strategy.py @@ -17,18 +17,20 @@ class BaseStrategy(metaclass=abc.ABCMeta): - def __init__(self, bot_id: int, side: BaseSide, pair: str) -> None: + def __init__(self, bot_id: int, side: BaseSide, pair: str, position: Union[int, None] = None) -> None: self.bot_id = bot_id self.side = side self.exchange = side.exchange self.pair = pair + self.position = position self.contract_size = Decimal(self.exchange.get_market(pair=pair)['contractSize']) self.strategy_helper = StrategyHelper(taker_fee=Decimal(self.exchange.ccxt_exchange.market(self.pair)['taker']), side=side.get_side_type(), contract_size=self.contract_size) - self.db_helper = StrategyDBHelper(side=side.get_side_type(), bot_id=self.bot_id, pair=self.pair) + self.db_helper = StrategyDBHelper(side=side.get_side_type(), bot_id=self.bot_id, pair=self.pair, + position=self.position) # Abstract methods @abc.abstractmethod @@ -36,11 +38,11 @@ async def open_deal_process(self, base_amount: Decimal): pass @abc.abstractmethod - async def close_deal_process(self, amount: float = None, deal: Union[Deal, None] = None) -> ClosedDeal: + async def close_deal_process(self, deal: Deal, amount: float = None) -> ClosedDeal: pass @abc.abstractmethod - async def average_deal_process(self, base_amount: Decimal): + async def average_deal_process(self, deal: Deal, base_amount: Decimal): pass # Helpers @@ -60,9 +62,9 @@ async def open_deal(self, amount: float) -> OpenedDealMessage: side=self.side.get_side_type() ) - async def average_deal(self, amount: float) -> AveragedDealMessage: + async def average_deal(self, amount: float, deal: Deal) -> AveragedDealMessage: base_amount = self.get_base_amount(quote_amount=Decimal(amount)) - safety_count, quote_amount = await self.average_deal_process(base_amount=base_amount) + safety_count, quote_amount = await self.average_deal_process(deal=deal, base_amount=base_amount) deal = await self.db_helper.get_deal() deal_stats = await self.db_helper.get_deal_stats(deal_id=deal.id) @@ -81,8 +83,8 @@ async def average_deal(self, amount: float) -> AveragedDealMessage: self.contract_size) ) - async def close_deal(self, amount: float, deal: Union[Deal, None] = None) -> ClosedDealMessage: - closed_deal = await self.close_deal_process(amount=amount, deal=deal) + async def close_deal(self, amount: float, deal: Deal) -> ClosedDealMessage: + closed_deal = await self.close_deal_process(deal=deal, amount=amount) return ClosedDealMessage( title=f'Bot id: {self.bot_id} ({self.exchange.get_exchange_name()})', diff --git a/src/bot/exchange/strategies/grid_strategy.py b/src/bot/exchange/strategies/grid_strategy.py index 4821224..4ece32b 100644 --- a/src/bot/exchange/strategies/grid_strategy.py +++ b/src/bot/exchange/strategies/grid_strategy.py @@ -20,27 +20,22 @@ async def open_deal_process(self, base_amount: Decimal): return order.quote_amount, price - async def average_deal_process(self, base_amount: Decimal): + async def average_deal_process(self, deal: Deal, base_amount: Decimal): # Not necessary for current strategy # self.ensure_deal_opened() self.set_leverage(20) order = self.average_market_order(amount=base_amount) - deal = await self.db_helper.get_or_create_deal() - await self.db_helper.create_average_order(deal_id=deal.id, price=order.price, volume=order.volume) safety_count = await self.db_helper.average_deal(deal_id=deal.id) return safety_count, order.quote_amount - async def close_deal_process(self, amount: float = None, deal: Union[Deal, None] = None) -> ClosedDeal: + async def close_deal_process(self, deal: Deal, amount: float = None) -> ClosedDeal: self.ensure_deal_opened() - if not deal: - deal = await self.db_helper.get_deal() - deal_stats = await self.db_helper.get_deal_stats(deal_id=deal.id) order = self.close_market_order(deal_stats.total_volume) diff --git a/src/bot/exchange/strategy_db_helper.py b/src/bot/exchange/strategy_db_helper.py index 7b53957..e58b0e1 100644 --- a/src/bot/exchange/strategy_db_helper.py +++ b/src/bot/exchange/strategy_db_helper.py @@ -1,6 +1,6 @@ from datetime import datetime from decimal import Decimal -from typing import Literal +from typing import Literal, Union from src.app.models import Order, Deal from src.app.services.deal import update_deal, get_deal, create_deal, increment_safety_orders_count, get_or_create_deal @@ -11,10 +11,11 @@ class StrategyDBHelper: - def __init__(self, side: SideType, bot_id: int, pair: str): + def __init__(self, side: SideType, bot_id: int, pair: str, position: Union[int, None] = None): self.side = side self.bot_id = bot_id self.pair = pair + self.position = position def get_order_side(self, action: Literal['open', 'close']) -> OrderSideType: mapping = { @@ -45,7 +46,7 @@ async def get_or_create_deal(self) -> Deal: return await get_or_create_deal(bot_id=self.bot_id, pair=self.pair) async def open_deal(self) -> Deal: - return await create_deal(bot_id=self.bot_id, pair=self.pair, date_open=datetime.now()) + return await create_deal(bot_id=self.bot_id, pair=self.pair, date_open=datetime.now(), position=self.position) async def get_deal_stats(self, deal_id: int) -> DealStats: return await get_deal_stats(deal_id=deal_id) diff --git a/src/bot/signal_dispatcher.py b/src/bot/signal_dispatcher.py index 65cd72f..864e442 100644 --- a/src/bot/signal_dispatcher.py +++ b/src/bot/signal_dispatcher.py @@ -10,7 +10,7 @@ from src.bot.exchange.notifiers.telegram_notifier import TelegramNotifier from .exceptions.not_found_exception import NotFoundException from .singal_dispatcher_spawner import spawn_and_dispatch -from src.app.services.deal import get_deal_by_id +from src.app.services.deal import get_deal_by_id, get_deal class SignalDispatcher: @@ -25,10 +25,12 @@ def __init__( async def dispatch(self): try: self.notifier.add_message_to_stack( + f'Position: {self.signal.position}\n' f'Received {self.signal.type_of_signal} signal: pair: {self.signal.pair}' + ( f' amount: {self.signal.amount}' if hasattr(self.signal, 'amount') else '')) - bot = Bot(bot_id=self.signal.bot_id, pair=self.signal.pair, type_of_signal=self.signal.type_of_signal) + bot = Bot(bot_id=self.signal.bot_id, pair=self.signal.pair, type_of_signal=self.signal.type_of_signal, + position=self.signal.position) for copy_bot in await bot.get_copy_bots(): copy_signal = deepcopy(self.signal) @@ -70,12 +72,21 @@ async def handle_open_signal(self): self.notifier.add_message_to_stack(str(opened_position_message)) async def handle_average_signal(self): - averaged_position_message = await self.strategy.average_deal(amount=self.signal.amount) + ### get_or_create_deal + + deal = await self.get_deal_from_signal() + averaged_position_message = await self.strategy.average_deal(amount=self.signal.amount, deal=deal) self.notifier.add_message_to_stack(str(averaged_position_message)) async def handle_close_signal(self): - deal = await get_deal_by_id(deal_id=self.signal.deal_id) + deal = await self.get_deal_from_signal() closed_position_message = await self.strategy.close_deal(amount=self.signal.amount, deal=deal) self.notifier.add_message_to_stack(str(closed_position_message)) + + async def get_deal_from_signal(self): + if self.signal.deal_id: + return await get_deal_by_id(deal_id=self.signal.deal_id) + else: + return await get_deal(bot_id=self.signal.bot_id, pair=self.strategy.pair, position=self.signal.position)