Parquer sa liquidité sur le marché crypto, c'est un casse-tête sans fin. Si tu gardes tes stablecoins au spot en attendant que le BTC ou l'ETH dip, ton capital se fait grignoter par l'inflation. Si tu les places en lending, tu grattes un misérable 2 à 4 % d'APY, le tout en te mangeant le risque de smart contract.
Le Grid Trading au spot sur des paires comme USDT/USDC ou USDC/DAI permet de sortir du 3 à 8 % d'APR sur du pur range. Sans levier, sans risque de liquidation, et sans les bobards des vendeurs de rêve qui te promettent 50 % par an sur du flat « sans risque ».
Toute la stratégie repose sur une réalité toute bête : les stablecoins ne valent jamais exactement $1.0000. Entre les rotations de capital entre protocoles, l'arbitrage et les cash-out en fiat, le cours du pair USDT/USDC oscille en permanence dans un canal hyper serré entre $0.9980 et $1.0020. Le grid de limites ne fait qu'empiler du yield sur chaque micro-oscillation.
Le piège des frais : là où tous les débutants se font rincer
La raison n°1 pour laquelle 90 % des bots de grid sur stablecoins tournent à perte, c'est le Fee Drag — l'érosion par les frais.
Imagine le topo : tu configures ton grid avec un pas de 0.01 % (c'est-à-dire des ordres placés tous les $0.0001). Tes frais Maker sur l'exchange sont au tarif standard de 0.02 %. Tu buy à $0.9999, tu sell à $1.0000. Sur le papier, tu prends 0.01 %. En réalité, tu as douillé 0.02 % à l'entrée et 0.02 % à la sortie. Bilan des courses : tu perds 0.03 % à chaque aller-retour.
| Retiens bien la règle d'or : le pas de ton grid doit impérativement être au moins 2.5 à 3 fois supérieur aux frais totaux de l'aller-retour (Maker Buy + Maker Sell). |
De là découle une condition sine qua non : faire tourner un Grid sur des stables n'a de sens que dans deux cas de figure :
- L'exchange propose une promo Zero Fee (0 % Maker / 0 % Taker) sur des paires comme USDT/USDC ou USDC/DAI.
- Tu as un statut VIP sur un CEX avec des frais Maker nuls ou négatifs (Rebate).
La réalité économique et les réglages des grids
Oublie les tableaux mirobolants à 30–50 % d'APY en spot. Voici les vrais chiffres que le marché crache au vu de la profondeur de l'order book et de la volatilité intraday :
| Paire | Type de peg | Range de travail | Pas optimal | APR net réel |
|---|---|---|---|---|
| USDT/USDC | Fiat / Fiat | 0.9985 - 1.0015 | 0.015% - 0.02% | 3% - 6% |
| USDC/DAI | Fiat / Collatéralisé Crypto | 0.9970 - 1.0030 | 0.02% - 0.03% | 4% - 8% |
| USDT/USDE | Fiat / Synthétique Delta-Neutre | 0.9930 - 1.0070 | 0.05% | 8% - 13% |
Si on te promet mieux que ça en pur spot, c'est soit un scam, soit la paire embarque un gros risque de depeg en cascade.
Exemple de sizing pour un capital de $10,000
Prenons la paire USDT/USDC. Le cours est pile à $1.0000.
- On cale un range de $0.9990 à $1.0010 (soit 0.2 % d'amplitude).
- Nombre de niveaux (Grids) : 20 niveaux.
- Pas du grid :
($1.0010 - $0.9990) / 20 = $0.0001(0.01 %). - Taille d'ordre par niveau :
$10,000 / 20 = $500. - Profit brut par aller-retour (Buy + Sell) :
$500 * 0.01% = $0.05.
Avec une volatilité classique, le bot boucle entre 60 et 140 allers-retours par 24h. En cash, ça fait $3.0 à $7.0 de profit net jour pour $10,000 posés, à zéro frais. Sur l'année, ça te sort un très propre 4 à 6 % d'APR sans bloquer tes fonds dans des smart contracts douteux.
Bot Python clé en main pour du grid spot
Le script ci-dessous tourne en async via WebSocket (ccxt.pro). Il pull l'order book en temps réel, pose le grid de buy/sell limit autour du prix actuel, puis écoute les fills en direct pour repasser instantanément l'ordre inverse un cran plus haut ou plus bas.
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"
)Le seul vrai risque qui peut raser ton capital
En grid trading sur stables, aucun risque de liquidation par effet de levier, mais tu te manges le risque de depeg en cascade.
Si l'un des deux stablecoins du pair commence à perdre son peg et décroche vers les $0.90, le bot va religieusement buy chaque palier dans la baisse avec la totalité du cash disponible. Résultat des courses : tu te retrouves coincé avec 100 % d'un token qui ne vaut plus rien.
Comment éviter de se faire rekt :
- Stop-Loss ultra strict : Pose un stop d'urgence juste en dessous de la borne basse de ton grid. Sur USDC/USDT, tu le cales à $0.9940. Prendre 0.5 % de perte sur ton bag est mille fois plus smart que de te retrouver bloqué 6 mois avec un token moribond.
- Check de la pool Curve 3pool : Les déséquilibres sur les pools DeFi crament toujours l'info avant que ça ne dumpe sur les CEX. Si la part d'un stable dépasse 60–65 % sur Curve, c'est le signal d'alarme : tu cut le bot immédiatement.
- Pas de marge, point bar : Vouloir faire tourner un grid sur stables avec un levier 10x pour gratter du « gros APY », c'est transformer un parking à liquidité pépère en casino où le moindre hoquet du marché t'envoie ad patres sur liquidation.
Applique ces trois règles, garde un œil rivé sur les frais de ton exchange, ne cherche pas à voir trop large sur le range, et ton grid spot deviendra une vraie machine à cash passif.