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
3 changes: 2 additions & 1 deletion src/infinity_grid/models/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class AssetPairInfoSchema(BaseModel):
str #: The asset class of the quote (e.g. "currency", "tokenized_asset")
)

cost_decimals: int #: Number of decimals for cost, e.g. 5
lot_decimals: int #: Number of decimals for lot/base size, e.g. 8
cost_decimals: int #: Number of decimals for cost/quote, e.g. 5
#: Fees for maker orders, e.g. [[0, 0.25], [10000, 0.2], ...]
fees_maker: list[list[float]] = Field(..., description="Maker fees structure")

Expand Down
2 changes: 1 addition & 1 deletion src/infinity_grid/strategies/grid_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ def handle_filled_order_event(self: Self, txid: str) -> None:
txid_to_delete=txid,
)
else:
# Remove filled order from list of all orders
# Remove filled order from orderbook
self._orderbook_table.remove(filters={"txid": txid})

def _handle_cancel_order(self: Self, txid: str) -> None:
Expand Down
5 changes: 1 addition & 4 deletions src/infinity_grid/strategies/grid_hodl.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def _new_sell_order(
# ======================================================================
volume: float | None = None
if txid_to_delete is not None: # If corresponding buy order filled
# GridSell always has txid_to_delete set.

# Add the txid of the corresponding buy order to the unsold buy
# order txids in order to ensure that the corresponding sell order
Expand All @@ -58,9 +57,7 @@ def _new_sell_order(
# ==================================================================
# Get the corresponding buy order in order to retrieve the volume.
corresponding_buy_order: OrderInfoSchema = (
self._rest_api.get_order_with_retry(
txid=txid_to_delete,
)
self._rest_api.get_order_with_retry(txid=txid_to_delete)
)

# In some cases the corresponding buy order is not closed yet and
Expand Down
38 changes: 30 additions & 8 deletions src/infinity_grid/strategies/grid_sell.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def _new_sell_order(
# ======================================================================
volume: float | None = None
if txid_to_delete is not None: # If corresponding buy order filled
# GridSell always has txid_to_delete set.
# GridSell almost always has txid_to_delete set, except for extra
# sell orders.

# Add the txid of the corresponding buy order to the unsold buy
# order txids in order to ensure that the corresponding sell order
Expand All @@ -78,9 +79,7 @@ def _new_sell_order(
# ==================================================================
# Get the corresponding buy order in order to retrieve the volume.
corresponding_buy_order: OrderInfoSchema = (
self._rest_api.get_order_with_retry(
txid=txid_to_delete,
)
self._rest_api.get_order_with_retry(txid=txid_to_delete)
)

# In some cases the corresponding buy order is not closed yet and
Expand Down Expand Up @@ -112,10 +111,7 @@ def _new_sell_order(
)

order_price = float(
self._rest_api.truncate(
amount=order_price,
amount_type="price",
),
self._rest_api.truncate(amount=order_price, amount_type="price"),
)

if volume is None:
Expand All @@ -136,6 +132,32 @@ def _new_sell_order(
# ======================================================================
# Check if there is enough base currency available for selling.
fetched_balances = self._rest_api.get_pair_balance()

# If there's not enough balance for the full volume, try with volume
# reduced by the smallest unit to account for potential floating-point
# precision issues (e.g., base_available = 0.012053559999999998 vs
# volume = 0.01205356). This might lead to an accumulation of dust,
# but is better than having sell orders not being placed. Open for
# alternative ideas!
if fetched_balances.base_available < volume:
lot_decimals = self._rest_api.get_asset_pair_info().lot_decimals
smallest_unit = Decimal(10) ** -lot_decimals
adjusted_volume = float(
Decimal(str(volume)) - (Decimal(10) ** -lot_decimals),
)

# Only use the adjusted volume if it's now within available balance
if fetched_balances.base_available >= adjusted_volume:
LOG.debug(
"Adjusting sell volume from %s to %s (reduced by %s) due to"
" insufficient balance. Available: %s",
volume,
adjusted_volume,
float(smallest_unit),
fetched_balances.base_available,
)
volume = adjusted_volume

if fetched_balances.base_available >= volume:
# Place new sell order, append id to pending list, and delete
# corresponding buy order from local orderbook.
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_exchange_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def test_valid_asset_pair_info(self) -> None:
base="XXBT",
quote="ZUSD",
cost_decimals=5,
lot_decimals=8,
fees_maker=[[0, 0.25], [10000, 0.2]],
aclass_base="currency",
aclass_quote="currency",
Expand All @@ -76,6 +77,7 @@ def test_empty_fees_maker(self) -> None:
base="ETH",
quote="USD",
cost_decimals=2,
lot_decimals=8,
fees_maker=[],
aclass_base="currency",
aclass_quote="currency",
Expand Down