Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions api/account/account.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import requests
import os
import pandas
from apis import BinbotApi
from tools.handle_error import (
handle_binance_errors,
Expand Down Expand Up @@ -85,7 +84,7 @@ def _get_price_from_book_order(self, data: dict, order_side: bool, index: int):
price, base_qty = data["bids"][index]
else:
price, base_qty = data["asks"][index]

return float(price), float(base_qty)

def ticker(self, symbol: str | None = None, json: bool = True):
Expand Down Expand Up @@ -255,7 +254,7 @@ def get_margin_balance(self, symbol="BTC") -> float:
symbol_balance = next((x["free"] for x in data if x["asset"] == symbol), 0)
return symbol_balance

def matching_engine(self, symbol: str, order_side: bool, qty: float=0) -> float:
def matching_engine(self, symbol: str, order_side: bool, qty: float = 0) -> float:
"""
Match quantity with available 100% fill order price,
so that order can immediately buy/sell
Expand All @@ -278,12 +277,16 @@ def matching_engine(self, symbol: str, order_side: bool, qty: float=0) -> float:
else:
total_length = len(data["bids"])
for i in range(1, total_length):
price, base_qty = self._get_price_from_book_order(data, order_side, i)
price, base_qty = self._get_price_from_book_order(
data, order_side, i
)
if buyable_qty > base_qty:
return price
else:
continue
raise ValueError("Unable to match base_order_size with available order prices")
raise ValueError(
"Unable to match base_order_size with available order prices"
)

def calculate_total_commissions(self, fills: dict) -> float:
"""
Expand Down
44 changes: 14 additions & 30 deletions api/bots/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
Strategy,
)
from deals.models import DealModel
from pydantic import (
BaseModel,
Field,
field_validator,
)
from pydantic import BaseModel, Field, field_validator
from database.utils import timestamp
from tools.handle_error import IResponseBase
from tools.enum_definitions import DealType, OrderType
from database.models.bot_table import BotTable
from database.models.deal_table import DealTable
from database.models.order_table import ExchangeOrderTable
from database.utils import Amount


class OrderModel(BaseModel):
Expand Down Expand Up @@ -54,8 +51,8 @@ def dump_from_table(cls, bot):
class BotBase(BaseModel):
pair: str
fiat: str = Field(default="USDC")
base_order_size: float = Field(
default=15, description="Min Binance 0.0001 BNB approx 15USD"
base_order_size: Amount = Field(
default=15, ge=0, description="Min Binance 0.0001 BNB approx 15USD"
)
candlestick_interval: BinanceKlineIntervals = Field(
default=BinanceKlineIntervals.fifteen_minutes,
Expand All @@ -65,6 +62,7 @@ class BotBase(BaseModel):
)
cooldown: int = Field(
default=0,
ge=0,
description="cooldown period in minutes before opening next bot with same pair",
)
created_at: float = Field(default_factory=timestamp)
Expand All @@ -74,29 +72,27 @@ class BotBase(BaseModel):
mode: str = Field(default="manual")
name: str = Field(default="Default bot")
status: Status = Field(default=Status.inactive)
stop_loss: float = Field(
default=0, description="If stop_loss > 0, allow for reversal"
stop_loss: Amount = Field(
default=0,
ge=-1,
le=101,
description="If stop_loss > 0, allow for reversal",
)
margin_short_reversal: bool = Field(default=False)
take_profit: float = Field(default=0)
take_profit: Amount = Field(default=0, ge=-1, le=101)
trailling: bool = Field(default=False)
trailling_deviation: float = Field(
trailling_deviation: Amount = Field(
default=0,
ge=-1,
le=101,
description="Trailling activation (first take profit hit)",
)
trailling_profit: float = Field(default=0)
trailling_profit: Amount = Field(default=0, ge=-1, le=101)
strategy: Strategy = Field(default=Strategy.long)
total_commission: float = Field(
default=0, description="autoswitch to short_strategy"
)


class BotPayload(BotBase):
id: Optional[str] = Field(default="")


class BotModel(BotBase):
"""
The way SQLModel works causes a lot of errors
Expand Down Expand Up @@ -128,7 +124,7 @@ class BotModel(BotBase):
"status": "inactive",
"stop_loss": 0,
"take_profit": 2.3,
"trailling": "true",
"trailling": True,
"trailling_deviation": 0.63,
"trailling_profit": 2.3,
"strategy": "long",
Expand All @@ -146,18 +142,6 @@ def deserialize_id(cls, v):
return str(v)
return True

@field_validator(
"take_profit",
"stop_loss",
"trailling_profit",
"trailling_deviation",
mode="before",
)
def convert_string_floats(cls, v):
if isinstance(v, str):
return float(v)
return True

@classmethod
def dump_from_table(cls, bot):
"""
Expand Down
14 changes: 8 additions & 6 deletions api/bots/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
from tools.exceptions import BinanceErrors, BinbotErrors
from deals.margin import MarginDeal
from deals.spot import SpotLongDeal
from uuid import UUID
from bots.models import BotModelResponse, BotPayload
from bots.models import BotModelResponse

bot_blueprint = APIRouter()
bot_ta = TypeAdapter(BotResponse)
Expand All @@ -41,7 +40,7 @@ def get(
# Has to be converted to BotModel to
# be able to serialize nested objects
ta = TypeAdapter(list[BotModelResponse])
data = ta.dump_python(bots)
data = ta.dump_python(bots) # type: ignore
response = BotListResponse(message="Successfully found bots!", data=data)
return response
except ValidationError as error:
Expand Down Expand Up @@ -114,13 +113,14 @@ def create(
@bot_blueprint.put("/bot/{id}", response_model=BotResponse, tags=["bots"])
def edit(
id: str,
bot_item: BotPayload,
bot_item: BotBase,
session: Session = Depends(get_session),
):
try:
bot_item.id = UUID(id)

controller = BotTableCrud(session=session)
bot_table = controller.get_one(bot_item)
bot_table = controller.get_one(id)
bot_table.sqlmodel_update(bot_item)

# bottable crud save only accepts BotModel
# this is to keep consistency across the entire app
Expand All @@ -134,6 +134,8 @@ def edit(
}
except ValidationError as error:
return BotResponse(message=f"Failed to edit bot: {error.json()}", error=1)
except BinbotErrors as error:
return BotResponse(message=error.message, error=1)


@bot_blueprint.delete("/bot", response_model=IResponseBase, tags=["bots"])
Expand Down
8 changes: 4 additions & 4 deletions api/database/api_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ def init_autotrade_settings(self):
autotrade_data = AutotradeTable(
id=AutotradeSettingsDocument.settings,
balance_to_use="USDC",
base_order_size=15,
base_order_size=20,
candlestick_interval=BinanceKlineIntervals.fifteen_minutes,
max_active_autotrade_bots=1,
max_active_autotrade_bots=3,
max_request=500,
stop_loss=0,
stop_loss=3,
take_profit=2.3,
telegram_signals=True,
trailling=True,
trailling_deviation=0.63,
trailling_deviation=1.63,
trailling_profit=2.3,
autotrade=True,
)
Expand Down
1 change: 1 addition & 0 deletions api/database/autotrade_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from tools.enum_definitions import AutotradeSettingsDocument
from autotrade.schemas import AutotradeSettingsSchema


class AutotradeCrud:
"""
Database operations for Autotrade settings
Expand Down
41 changes: 19 additions & 22 deletions api/database/bot_crud.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from typing import List, Optional
from uuid import UUID
from fastapi import Query
from sqlmodel import Session, asc, desc, or_, select, case
from database.utils import timestamp
from sqlmodel import Session, asc, desc, select, case
from bots.models import BotModel
from database.models.bot_table import BotTable
from database.models.deal_table import DealTable
Expand All @@ -12,7 +11,7 @@
from bots.models import BotBase
from collections.abc import Sequence
from sqlalchemy.orm.attributes import flag_modified
from tools.exceptions import SaveBotError
from tools.exceptions import SaveBotError, BinbotErrors


class BotTableCrud:
Expand Down Expand Up @@ -272,37 +271,35 @@ def get_active_pairs(self) -> list:
return []
return list(pairs)

def get_order(self, order_id: int) -> ExchangeOrderTable:
"""
Get one order by order_id
"""
statement = select(ExchangeOrderTable).where(
ExchangeOrderTable.order_id == order_id
)
order = self.session.exec(statement).first()
# self.session.close()
if order:
return order
else:
raise BinbotErrors("Order not found")

def update_order(
self, order: ExchangeOrderTable, commission: float
) -> ExchangeOrderTable:
"""
Update order data
"""
statement = select(ExchangeOrderTable).where(
ExchangeOrderTable.order_id == order.order_id
)
initial_order = self.session.exec(statement).first()

if not initial_order:
raise ValueError("Order not found")

initial_order.status = order.status
initial_order.qty = order.qty
initial_order.order_side = order.order_side
initial_order.order_type = order.order_type
initial_order.timestamp = order.timestamp
initial_order.pair = order.pair
initial_order.time_in_force = order.time_in_force
initial_order.price = order.price

initial_bot = self.get_one(bot_id=str(initial_order.bot_id))
initial_bot = self.get_one(bot_id=str(order.bot_id))
initial_bot.total_commission += commission
initial_bot.logs.append("Order status updated")

self.session.add(initial_order)
self.session.add(order)
self.session.add(initial_bot)
self.session.commit()
self.session.refresh(initial_order)
self.session.refresh(order)
self.session.refresh(initial_bot)
self.session.close()
return order
18 changes: 15 additions & 3 deletions api/database/models/account_balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class ExchangeInfoTable(SQLModel, table=True):
base_asset: str = Field(
index=True, description="Base asset of the symbol e.g. BTCUSDC -> BTC"
)
binance_exchange_info: dict = Field(sa_column=Column(JSON), description="For now, this is the full binance exchange info to make things easier")
binance_exchange_info: dict = Field(
sa_column=Column(JSON),
description="For now, this is the full binance exchange info to make things easier",
)
created_at: float = Field(default_factory=timestamp)
updated_at: float = Field(default_factory=timestamp)

Expand All @@ -46,7 +49,16 @@ class ConsolidatedBalancesTable(SQLModel, table=True):

__tablename__ = "consolidated_balances"

id: float = Field(default_factory=timestamp, primary_key=True, unique=True, index=True, nullable=False)
id: float = Field(
default_factory=timestamp,
primary_key=True,
unique=True,
index=True,
nullable=False,
)
asset: str = Field(index=True, nullable=False)
free: Optional[float] = Field(default=0)
estimated_total_fiat: Optional[float] = Field(default=0, description="This is derived from free * price of fiat, which is determined in autotrade")
estimated_total_fiat: Optional[float] = Field(
default=0,
description="This is derived from free * price of fiat, which is determined in autotrade",
)
8 changes: 4 additions & 4 deletions api/database/models/bot_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ class BotTable(SQLModel, table=True):
back_populates="bot",
sa_relationship_kwargs={"lazy": "joined"},
)
deal_id: Optional[UUID] = Field(default=None, foreign_key="deal.id", ondelete="CASCADE")
# lazy option will allow objects to be nested when transformed for json return
deal: DealTable = Relationship(
sa_relationship_kwargs={"lazy": "joined"}
deal_id: Optional[UUID] = Field(
default=None, foreign_key="deal.id", ondelete="CASCADE"
)
# lazy option will allow objects to be nested when transformed for json return
deal: DealTable = Relationship(sa_relationship_kwargs={"lazy": "joined"})

model_config = {
"from_attributes": True,
Expand Down
Loading
Loading