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
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""add signal ids to bots and grid ladders

Revision ID: c5d6e7f8a9b0
Revises: b1c2d3e4f5a6
Create Date: 2026-08-15 00:00:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

revision: str = "c5d6e7f8a9b0"
down_revision: str | Sequence[str] | None = "b1c2d3e4f5a6"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.add_column("bot", sa.Column("signal_id", sa.Integer(), nullable=True))
op.create_foreign_key(
"fk_bot_signal_id_signals",
"bot",
"signals",
["signal_id"],
["id"],
ondelete="SET NULL",
)
op.create_index("ix_bot_signal_id", "bot", ["signal_id"], unique=False)

op.add_column("grid_ladder", sa.Column("signal_id", sa.Integer(), nullable=True))
op.create_foreign_key(
"fk_grid_ladder_signal_id_signals",
"grid_ladder",
"signals",
["signal_id"],
["id"],
ondelete="SET NULL",
)
op.create_index(
"ix_grid_ladder_signal_id", "grid_ladder", ["signal_id"], unique=False
)


def downgrade() -> None:
op.drop_index("ix_grid_ladder_signal_id", table_name="grid_ladder")
op.drop_constraint(
"fk_grid_ladder_signal_id_signals",
"grid_ladder",
type_="foreignkey",
)
op.drop_column("grid_ladder", "signal_id")

op.drop_index("ix_bot_signal_id", table_name="bot")
op.drop_constraint("fk_bot_signal_id_signals", "bot", type_="foreignkey")
op.drop_column("bot", "signal_id")
2 changes: 2 additions & 0 deletions api/databases/crud/grid_ladder_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(self, session: Session):
def create(
self,
*,
signal_id: int | None = None,
symbol: str,
fiat: str,
exchange: ExchangeId | str,
Expand All @@ -71,6 +72,7 @@ def create(
context: dict,
) -> GridLadderTable:
ladder = GridLadderTable(
signal_id=signal_id,
symbol=symbol,
fiat=fiat,
exchange=exchange,
Expand Down
3 changes: 3 additions & 0 deletions api/databases/tables/bot_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class BotTable(SQLModel, table=True):
id: Optional[UUID] = Field(
default_factory=uuid4, primary_key=True, index=True, nullable=False, unique=True
)
signal_id: int | None = Field(
default=None, foreign_key="signals.id", ondelete="SET NULL", index=True
)
Comment thread
carkod marked this conversation as resolved.
pair: str = Field(index=True)
fiat: str = Field(default="USDC", index=True)
quote_asset: QuoteAssets = Field(default=QuoteAssets.USDC)
Expand Down
3 changes: 3 additions & 0 deletions api/databases/tables/grid_ladder_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class GridLadderTable(SQLModel, table=True):
)

id: UUID = Field(default_factory=uuid4, primary_key=True, index=True, unique=True)
signal_id: int | None = Field(
default=None, foreign_key="signals.id", ondelete="SET NULL", index=True
)
symbol: str = Field(index=True)
fiat: str = Field(default="USDC", index=True)
exchange: ExchangeId = Field(
Expand Down
1 change: 1 addition & 0 deletions api/grid_ladders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


class GridLadderCreate(BaseModel):
signal_id: int | None = None
symbol: str = Field(min_length=1)
fiat: str = Field(default="USDC", min_length=1)
exchange: ExchangeId = Field(default=ExchangeId.KUCOIN)
Expand Down
1 change: 1 addition & 0 deletions api/grid_ladders/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def post_grid_ladder(

reserved_margin = sum(level.margin_required for level in calculated.levels)
ladder = grid_ladder_crud.create(
signal_id=payload.signal_id,
symbol=payload.symbol,
fiat=payload.fiat,
exchange=payload.exchange,
Expand Down
22 changes: 20 additions & 2 deletions api/tests/test_bots.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import UTC, datetime
from unittest.mock import patch
from fastapi.testclient import TestClient
from pytest import fixture, raises
Expand All @@ -16,6 +17,7 @@
from api.databases.tables.deal_table import DealTable
from api.databases.tables.grid_ladder_table import GridLadderTable
from api.databases.tables.recovery_bot_table import RecoveryBotTable
from api.databases.tables.signals_table import SignalsTable
from uuid import UUID, uuid4


Expand Down Expand Up @@ -191,8 +193,23 @@ def test_get_algo_ranking_ordered_by_count(client: TestClient):
assert profits == sorted(profits, reverse=True)


def test_create_bot(client: TestClient):
response = client.post("/bot", json=mock_bot_data_superusdt)
def test_create_bot_persists_signal_id(client: TestClient, create_test_tables):
signal_id = 9001
with Session(create_test_tables) as session:
session.add(
SignalsTable(
id=signal_id,
algorithm_name="test_bot",
symbol=mock_bot_data_superusdt["pair"],
generated_at=datetime.now(UTC),
direction="long",
)
)
session.commit()

response = client.post(
"/bot", json={**mock_bot_data_superusdt, "signal_id": signal_id}
)

assert response.status_code == 200
content = response.json()
Expand All @@ -202,6 +219,7 @@ def test_create_bot(client: TestClient):
assert (
content["data"]["fiat_order_size"] == mock_bot_data_superusdt["fiat_order_size"]
)
assert content["data"]["signal_id"] == signal_id


def test_create_bot_without_recovery_params_creates_no_recovery_row(
Expand Down
23 changes: 21 additions & 2 deletions api/tests/test_grid_ladders.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,15 +625,34 @@ def test_calculate_grid_ladder_disables_unaffordable_levels(client, monkeypatch)
assert all(level["side"] == "neutral" for level in detail["levels"])


def test_post_grid_ladder_persists_ladder_and_levels(client, monkeypatch):
def test_post_grid_ladder_persists_ladder_signal_and_levels(
client, monkeypatch, create_test_tables
):
_patch_balance(monkeypatch, 10_000)
_patch_contract_meta(monkeypatch)
signal_id = 9002
with Session(create_test_tables) as session:
session.add(
SignalsTable(
id=signal_id,
algorithm_name="fixed_grid",
symbol="ADAUSDC",
generated_at=datetime.now(UTC),
direction="grid",
signal_kind="grid_deploy",
)
)
session.commit()

response = client.post("/grid-ladders", json=_payload())
payload = _payload()
payload["signal_id"] = signal_id

response = client.post("/grid-ladders", json=payload)

assert response.status_code == 200
body = response.json()
ladder = body["detail"]
assert ladder["signal_id"] == signal_id
assert ladder["symbol"] == "ADAUSDC"
assert ladder["status"] == "pending"
assert ladder["grid_step"] == 5
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies = [
"sqlalchemy>=2.0.30",
"alembic>=1.18.4",
"alembic-postgresql-enum",
"pybinbot>=1.11.13",
"pybinbot>=1.11.15",
]

[project.urls]
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading