Паркинг ликвидности на крипторынке - это вечная проблема. Держишь стейблкоины на споте в ожидании просадки BTC или ETH - капитал обесценивается инфляцией. Отдаешь в лендинг - получаешь скромные 2–4% APY, да еще и с рисками смарт-контрактов.
Спотовый Grid-трейдинг на парах вроде USDT/USDC или USDC/DAI позволяет выжимать 3–8% APR на чистом боковике. Без плечей, без риска ликвидации и без инфоцыганских сказок про 50% годовых на “безопасном” флэте.
Вся стратегия строится на одном факте: стейблкоины не стоят ровно $1.0000. Из-за постоянных переливов капитала между протоколами, арбитража и вывода средств в фиат курс пары USDT/USDC непрерывно ходит в узком коридоре $0.9980–$1.0020. Сетка лимитных ордеров просто собирает профит с каждого такого микроколебания.
Заманиха с комиссиями: на чем сразу срезаются новички
Главная причина, почему 90% сеточных ботов на стейблах торгуют в минус - это Fee Drag, т.е комиссионный износ.
Представь: ты настраиваешь сетку с шагом 0.01% (это когда ордера расставлены каждые $0.0001). Твоя Maker-комиссия на бирже - стандартные 0.02%. Ты покупаешь по $0.9999, продаешь по $1.0000. На бумаге ты заработал 0.01%. На деле — заплатил 0.02% за вход и 0.02% за выход. Итог: минус 0.03% с каждого круга.
Запомни базовое правило: шаг сетки обязан быть минимум в 2.5–3 раза больше суммарной комиссии за круг (Maker Buy + Maker Sell).
Из этого следует жесткое условие: крутить Grid на стейблах имеет смысл только в двух случаях:
- На парах USDT/USDC или USDC/DAI действует акция Zero Fee (0% Maker / 0% Taker).
- У тебя раскачан VIP-уровень на CEX с нулевой или отрицательной Maker-комиссией (Rebate).
Реальная экономика и параметры сеток
Забудь про таблицы с 30–50% APY на споте. Вот реальные цифры, которые дает рынок исходя из глубины стаканов и суточной волатильности:
| Пара | Тип привязки | Рабочий диапазон | Оптимальный шаг | Реальный чистый APR |
|---|---|---|---|---|
| USDT/USDC | Fiat / Fiat | 0.9985 - 1.0015 | 0.015% - 0.02% | 3% - 6% |
| USDC/DAI | Fiat / Crypto-collateral | 0.9970 - 1.0030 | 0.02% - 0.03% | 4% - 8% |
| USDT/USDE | Fiat / Delta-neutral synthetic | 0.9930 - 1.0070 | 0.05% | 8% - 13% |
Если тебе обещают цифры выше на чистом споте - либо перед тобой мошенники, либо в пару заложен риск скорого каскадного депега.
Пример расчета на $10,000 депозита
Возьмем пару USDT/USDC. Курс ровно $1.0000.
- Выставляем диапазон $0.9990 – $1.0010 (размах 0.2%).
- Количество гридов (Grids): 20 уровней.
- Шаг сетки:
($1.0010 - $0.9990) / 20 = $0.0001(0.01%). - Размер ордера на один уровень:
$10,000 / 20 = $500. - Грязный профит с 1 круга (Buy + Sell):
$500 * 0.01% = $0.05.
При нормальной волатильности бот за сутки закрывает от 60 до 140 кругов. В деньгах это $3.0–$7.0 чистой прибыли с $10,000 депозита при нулевой комиссии. За год как раз набегает адекватные 4–6% APR без удержания средств в непонятных смарт-контрактах.
Готовый Python-бот для спотовой сетки
Скрипт ниже работает асинхронно через WebSocket (ccxt.pro). Он вытягивает текущий стакан, расставляет сетку лимиток от текущей цены, а дальше слушаем исполнения в реальном времени и сразу перевыставляет противоположный ордер на шаг выше или ниже.
import asyncio
import logging
from decimal import Decimal
import ccxt.pro as ccxt
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
class StableGridBot:
def __init__(
self,
symbol: str,
lower_price: float,
upper_price: float,
grids: int,
amount_per_grid: float,
api_key: str,
api_secret: str
):
self.symbol = symbol
self.lower_price = Decimal(str(lower_price))
self.upper_price = Decimal(str(upper_price))
self.grids = grids
self.amount_per_grid = amount_per_grid
self.step = (self.upper_price - self.lower_price) / Decimal(str(grids))
self.exchange = ccxt.binance({
'apiKey': api_key,
'secret': api_secret,
'enableRateLimit': True,
'options': {
'defaultType': 'spot'
}
})
self.active_orders = {}
async def initialize_grid(self):
await self.exchange.load_markets()
market = self.exchange.market(self.symbol)
min_cost = (
market
.get('limits', {})
.get('cost', {})
.get('min')
)
if min_cost:
order_cost = (
float(self.amount_per_grid)
* float(self.lower_price)
)
if order_cost < min_cost:
raise ValueError(
f"Order cost {order_cost} below minimum {min_cost}"
)
open_orders = await self.exchange.fetch_open_orders(
self.symbol
)
existing_prices = set()
if open_orders:
logging.info(
f"Found {len(open_orders)} open orders. "
f"Restoring state from exchange..."
)
for order in open_orders:
price_str = self.exchange.price_to_precision(
self.symbol,
order['price']
)
self.active_orders[order['id']] = (
order['side'],
float(price_str)
)
existing_prices.add(price_str)
ticker = await self.exchange.fetch_ticker(
self.symbol
)
current_price = Decimal(
str(ticker['last'])
)
logging.info(
f"Grid initialized. "
f"Current price for {self.symbol}: {current_price}"
)
for i in range(self.grids + 1):
raw_price = (
self.lower_price +
(Decimal(str(i)) * self.step)
)
price_str = self.exchange.price_to_precision(
self.symbol,
str(raw_price)
)
if price_str in existing_prices:
continue
price = float(price_str)
if Decimal(price_str) < current_price:
order = await self.place_order(
'buy',
price
)
if order:
self.active_orders[order['id']] = (
'buy',
price
)
elif Decimal(price_str) > current_price:
order = await self.place_order(
'sell',
price
)
if order:
self.active_orders[order['id']] = (
'sell',
price
)
async def place_order(
self,
side: str,
price: float
):
try:
precise_price = float(
self.exchange.price_to_precision(
self.symbol,
price
)
)
precise_amount = float(
self.exchange.amount_to_precision(
self.symbol,
self.amount_per_grid
)
)
order = await self.exchange.create_order(
symbol=self.symbol,
type='limit',
side=side,
amount=precise_amount,
price=precise_price
)
logging.info(
f"Placed {side.upper()} order at "
f"{precise_price}"
)
return order
except Exception as e:
logging.error(
f"Failed to place "
f"{side} order at {price}: {e}"
)
return None
async def handle_order_fill(
self,
filled_order
):
order_id = filled_order['id']
order_info = self.active_orders.pop(
order_id,
None
)
if order_info is None:
return
side, price = order_info
logging.info(
f"Filled {side.upper()} "
f"order at price: {price}"
)
dec_price = Decimal(str(price))
if side == 'buy':
new_price_dec = dec_price + self.step
if new_price_dec > self.upper_price:
logging.info(
f"Skipping SELL order: "
f"{new_price_dec} is above "
f"upper grid boundary"
)
return
new_price = float(
self.exchange.price_to_precision(
self.symbol,
str(new_price_dec)
)
)
new_order = await self.place_order(
'sell',
new_price
)
if new_order:
self.active_orders[new_order['id']] = (
'sell',
new_price
)
else:
new_price_dec = dec_price - self.step
if new_price_dec < self.lower_price:
logging.info(
f"Skipping BUY order: "
f"{new_price_dec} is below "
f"lower grid boundary"
)
return
new_price = float(
self.exchange.price_to_precision(
self.symbol,
str(new_price_dec)
)
)
new_order = await self.place_order(
'buy',
new_price
)
if new_order:
self.active_orders[new_order['id']] = (
'buy',
new_price
)
async def start(self):
await self.initialize_grid()
while True:
try:
orders = await self.exchange.watch_orders(
self.symbol
)
for order in orders:
if (
order.get('status') == 'closed'
and order.get('id') in self.active_orders
):
await self.handle_order_fill(
order
)
except Exception as e:
logging.error(
f"WebSocket error: {e}"
)
await asyncio.sleep(5)
async def close(self):
try:
await self.exchange.close()
except Exception:
pass
async def main():
bot = StableGridBot(
symbol='USDC/USDT',
lower_price=0.9985,
upper_price=1.0015,
grids=30,
amount_per_grid=500,
api_key='YOUR_API_KEY',
api_secret='YOUR_API_SECRET'
)
try:
await bot.start()
finally:
await bot.close()
if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
logging.info(
"Bot shut down by user"
)Единственный риск, который ликвидирует твой депозит
В грид-трейдинге на стейблах нет риска плеча, но есть риск каскадного депега.
Если один из стейблкоинов в паре начинает отвязываться от доллара и валится к $0.90, бот будет дисциплинированно выкупать каждое падающее колено на весь доступный депозит. В итоге ты останешься со 100% обесценившегося актива на руках.
Как не слиться:
- Жесткий Stop-Loss: Настрой аварийный стоп ниже границы сетки. Для пары USDC/USDT это уровень $0.9940. Закрыть 0.5% убытка по депозиту намного разумнее, чем застрять на полгода в проблемном токене.
- Мониторинг Curve 3pool: Дисбаланс в DeFi-пулах всегда опережает дампы на централизованных биржах. Если в Curve доля одного из стейблов превышает 60–65% — это прямой сигнал мгновенно снимать бота.
- Никакой маржи: Попытка запустить сетку на стейблах с плечом 10x ради "высокого APY" превращает рутинный паркинг ликвидности в рулетку, где любой чих рынка закроет тебя по ликвидации.
Соблюдай три эти правила, следи за комиссиями биржи и не держи диапазон слишком широким и тогда спотовый грид превратится в надежный генератор пассивного потока.