Grid Trading
/install grid-trading
Dynamic Grid Trading Strategy v1.0
Cron-driven grid bot for EVM L2 chains via onchainos CLI. Features asymmetric grid steps — different spacing for buy vs sell sides based on trend direction, trend intelligence with multi-timeframe analysis, sell optimization, and HODL Alpha tracking.
Every tick: fetch price → MTF analysis → compute grid level → trend-adaptive decision → execute swap → report to Discord.
Asymmetric Grid Steps
Buy and sell sides use different spacing based on trend direction:
| Trend | Buy Side | Sell Side | Effect |
|---|---|---|---|
| Bullish | Tighter (accumulate fast) | Wider (hold longer) | Buy dense, sell sparse → captures uptrend |
| Bearish | Wider (wait for dip) | Tighter (exit fast) | Sell dense, buy sparse → reduces downside exposure |
| Neutral/Weak | Symmetric | Symmetric | Symmetric (default) |
Key config: ASYM_FACTOR=0.4 (max asymmetry ratio). Asymmetry scales with trend strength and only activates when strength > 0.3.
New grid dict fields: buy_step, sell_step (backward-compatible step = average). Level prices are now non-uniform: below center spaced by buy_step, above center by sell_step.
Architecture
Cron (5min) → Python script → onchainos CLI → OKX Web3 API → Chain
↓ ↓
grid_state.json Wallet (TEE signing)
↓
┌─────────────┐
│ MTF Analysis │ ← price_history (288 bars = 24h)
│ K-line ATR │ ← okx-dex-market kline (1H × 24)
└──────┬──────┘
↓
Trend-Adaptive Grid Decision
↓
Discord embed (notification)
OKX Skill Dependencies (command syntax defined in each skill, do not duplicate here):
okx-dex-swap— quote, approve, swap executionokx-dex-market— K-line / OHLC dataokx-agentic-wallet— wallet switch, balance, contract-call (TEE signing)okx-onchain-gateway— transaction simulation
Pipeline: Execution Steps
CRITICAL RULE: Steps MUST execute in order. Do NOT skip steps or proceed past a gate that has not been satisfied.
Step 1: Data Acquisition
Actions:
- Fetch ETH price via
okx-dex-swap(swap quote) - Fetch on-chain balances via
okx-agentic-wallet(wallet balance) - Update
price_history(append, cap at 288 = 24h @ 5min) - Detect external deposits/withdrawals (unexplained balance changes > $100)
Gate (ALL must pass):
- Price is non-null and > 0
- At least one balance (ETH or USDC) > 0
- Circuit breaker not active (
consecutive_errors \x3C 5) - Stop not triggered (
stop_triggered == null)
Step 2: Multi-Timeframe Analysis
Actions:
- Compute short EMA (25min / 5-bar), medium EMA (1h / 12-bar), long EMA (4h / 48-bar)
- Detect EMA alignment → trend direction (bullish / bearish / neutral) + strength (0-1)
- Detect 8h structure: split into 4 segments, check higher-highs/higher-lows → uptrend / downtrend / ranging
- Compute 1h and 4h momentum
- Fetch K-line data (1H candles, 24 bars) → compute ATR-based volatility (hourly cache)
Output: mtf dict, kline_vol float
def analyze_multi_timeframe(history, price) -> dict:
# Returns {trend, strength, momentum_1h, momentum_4h, structure,
# ema_short, ema_medium, ema_long}
# EMA alignment: short > medium > long → bullish
# Structure: 8h window split into 4 segments
# higher-highs + higher-lows → "uptrend"
# lower-highs + lower-lows → "downtrend"
# else → "ranging"
Gate:
-
mtfdict hastrendandstrengthfields (graceful fallback to neutral if insufficient history)
Step 3: Grid Decision
Actions:
- Calculate dynamic grid with trend-adaptive volatility multiplier:
- Grid center = EMA(20) on 1H kline (20-hour EMA, fetched via
okx-dex-marketkline). Falls back to 5min tick history if kline unavailable. - Base:
VOLATILITY_MULTIPLIER_BASE=2.0 - In trend (strength > 0.3): blend toward
VOLATILITY_MULTIPLIER_TREND=3.0 - Wider grid in trends → fewer trades → more holding
- Grid center = EMA(20) on 1H kline (20-hour EMA, fetched via
- Check recalibration triggers (breakout / vol shift / age)
- Map price → grid level
- If level changed: determine direction (BUY if level dropped, SELL if rose)
- Safety checks: cooldown, trend-adaptive position limits, repeat guard, consecutive limit
- Sell optimization: if SELL in strong uptrend, delay via
_should_delay_sell() - Calculate trade size with trend-adaptive sizing
Gate:
- Grid is valid (step > 0, levels > 0)
- If trade needed: amount >=
MIN_TRADE_USD($5) - All safety checks passed (cooldown, position limits, etc.)
Step 4: Execution
Actions:
- Get swap quote + tx data from OKX DEX aggregator
- Pre-simulate via
okx-onchain-gateway(diagnostic, non-blocking) - For BUY: ensure USDC approval via
okx-dex-swap - Sign + broadcast via
okx-agentic-walletcontract-call (TEE signing) - On failure: 1 auto-retry with 3s delay and fresh quote
- Record trade in state, update grid level ONLY on success
Gate:
- Transaction hash received, or failure recorded with
retriableflag - Level updated only on success; NOT on failure or skip
Step 5: Notification & Tracking
Actions:
- Calculate PnL (USD + ETH denominated)
- Calculate HODL Alpha:
current_portfolio - (initial_eth × current_price) - Build structured JSON output for AI agent parsing
- Send Discord embed (green=SELL, blue=BUY, grey=no-trade, red=stop)
- Emit
---JSON---block with enriched fields
Output: Discord notification + structured JSON
Tunable Parameters
Grid Structure
| Parameter | Default | Description |
|---|---|---|
GRID_LEVELS |
6 |
Number of grid levels. More = finer, more trades |
GRID_TYPE |
"arithmetic" |
"arithmetic" (fixed $ step) or "geometric" (fixed % step) |
EMA_PERIOD |
20 |
EMA lookback for grid center (applied to 1H kline = 20h) |
VOLATILITY_MULTIPLIER_BASE |
2.0 |
Base grid width = multiplier x stddev |
VOLATILITY_MULTIPLIER_TREND |
3.0 |
Wider grid in trending markets |
ASYM_FACTOR |
0.4 |
Max buy/sell asymmetry ratio. 0=symmetric, 1=fully asymmetric |
GRID_RECALIBRATE_HOURS |
12 |
Max hours before forced recalibration |
Multi-Timeframe
| Parameter | Default | Description |
|---|---|---|
MTF_SHORT_PERIOD |
5 |
5-bar EMA (25min @ 5min tick) |
MTF_MEDIUM_PERIOD |
12 |
12-bar EMA (1h @ 5min tick) |
MTF_LONG_PERIOD |
48 |
48-bar EMA (4h @ 5min tick) |
MTF_STRUCTURE_PERIOD |
96 |
96-bar (8h @ 5min tick) for structure detection |
Sell Optimization
| Parameter | Default | Description |
|---|---|---|
SELL_TRAIL_TICKS |
2 |
Wait 2 ticks (10min) of price stability before selling in uptrend |
SELL_MOMENTUM_THRESHOLD |
0.005 |
Skip sell if 1h momentum > 0.5% in strong uptrend |
Grid Modes
Arithmetic (等差网格): Each level is a fixed USD distance apart. Good for narrow ranges.
levels = [center - N*step, ..., center, ..., center + N*step]
Geometric (等比网格): Each level is a fixed percentage apart. Better for wide ranges because step size scales with price. The ratio is derived from the arithmetic step: ratio = 1 + (step / center).
# In calc_dynamic_grid(), when GRID_TYPE == "geometric":
ratio = 1 + (step / center)
level_prices = [center * (ratio ** (i - half)) for i in range(GRID_LEVELS + 1)]
Both modes store level_prices in the grid dict for unified level lookup via bisect_right. Asymmetric spacing uses levels below center spaced by buy_step, levels above by sell_step. The _build_level_prices() helper handles both symmetric and asymmetric construction.
Choosing a mode:
| Market | Recommended | Why |
|---|---|---|
| Tight range ($1900-$2100) | Arithmetic | Even spacing, predictable profit per grid |
| Wide range ($1500-$3000) | Geometric | Steps scale with price, avoids crowding at low end |
| High volatility | Geometric | Naturally wider steps at higher prices |
| Stablecoin pairs | Arithmetic | Fixed small steps (0.1-0.5%) |
Adaptive Step Sizing
Step scales with real-time volatility, modulated by trend strength. Splits into directional buy/sell steps:
vol_mult = VOLATILITY_MULTIPLIER_BASE (2.0)
if trend_strength > 0.3:
vol_mult = blend(BASE, TREND, strength) (up to 3.0)
# Asymmetric steps based on trend direction
asym = ASYM_FACTOR × strength (if strength > 0.3, else 0)
if bullish:
buy_mult = vol_mult × (1 - asym) # tighter buy
sell_mult = vol_mult × (1 + asym) # wider sell
elif bearish:
buy_mult = vol_mult × (1 + asym) # wider buy
sell_mult = vol_mult × (1 - asym) # tighter sell
buy_step = (buy_mult × ATR) / (GRID_LEVELS / 2)
sell_step = (sell_mult × ATR) / (GRID_LEVELS / 2)
# Both clamped to [price × STEP_MIN_PCT, price × STEP_MAX_PCT], floor $5
step = (buy_step + sell_step) / 2 # backward-compatible average
| Parameter | Default | Description |
|---|---|---|
STEP_MIN_PCT |
0.010 |
Step floor as fraction of price (1.0%) |
STEP_MAX_PCT |
0.060 |
Step cap as fraction of price (6%) |
VOL_RECALIBRATE_RATIO |
0.3 |
Recalibrate if kline ATR shifts >30% from grid's stored ATR |
Recalibration triggers (asymmetric):
- Downside breakout: Price \x3C grid lower -
buy_step→ recalibrate immediately (buying dips is grid's edge) - Upside breakout: Price > grid upper +
sell_step→ require N consecutive ticks confirmation before recalibrating (anti-chase) - Grid age exceeds
GRID_RECALIBRATE_HOURS - Current kline ATR deviates >30% from grid's stored ATR
| Parameter | Default | Description |
|---|---|---|
UPSIDE_CONFIRM_TICKS |
6 |
Ticks (30min @ 5min interval) price must hold above grid before upside recalibration |
MAX_CENTER_SHIFT_PCT |
0.03 |
Max 3% grid center shift per recalibration (prevents chasing spikes) |
Anti-chase mechanism:
- Upside breakout counter resets if price returns to grid range before confirmation
- Even after confirmation, center shifts are capped to
MAX_CENTER_SHIFT_PCTper recalibration - Multiple recalibrations can gradually track a true trend, but a single spike cannot drag the grid
Position Sizing Strategies
Controls how much to trade at each grid level.
| Strategy | Description |
|---|---|
"equal" |
Every grid level trades the same amount |
"martingale" |
BUY more at lower levels, SELL more at higher levels |
"anti_martingale" |
Reduces exposure as price moves further from center |
"pyramid" |
Largest position at grid center, tapering toward edges |
"trend_adaptive" |
(default) In bullish: buy more + sell less. In bearish: sell more + buy less |
def _calc_sizing_multiplier(level, grid_levels, direction, mtf=None):
base_mult = 1.0
if SIZING_STRATEGY == "trend_adaptive" and mtf:
trend = mtf.get("trend", "neutral")
strength = mtf.get("strength", 0)
if trend == "bullish":
if direction == "BUY":
base_mult = 1.0 + strength * (MAX - 1.0) # buy aggressively
else:
base_mult = 1.0 - strength * (1.0 - MIN) # sell conservatively
elif trend == "bearish":
# opposite
...
return clamp(base_mult, SIZING_MULTIPLIER_MIN, SIZING_MULTIPLIER_MAX)
Trade Execution
| Parameter | Default | Description |
|---|---|---|
MAX_TRADE_PCT |
0.12 |
Max 12% of portfolio per trade (before sizing multiplier) |
MIN_TRADE_USD |
5.0 |
Minimum trade size in USD |
SLIPPAGE_PCT |
1 |
Slippage tolerance for DEX swap |
GAS_RESERVE |
0.003 |
Native token reserved for gas |
Risk Controls
Basic Controls
| Parameter | Default | Description |
|---|---|---|
MIN_TRADE_INTERVAL |
1800 |
30min cooldown between same-direction trades |
MAX_SAME_DIR_TRADES |
3 |
Max consecutive same-direction trades |
MAX_CONSECUTIVE_ERRORS |
5 |
Circuit breaker threshold |
COOLDOWN_AFTER_ERRORS |
3600 |
Cooldown after circuit breaker trips |
POSITION_MAX_PCT_DEFAULT |
70 |
Block BUY when ETH > this % |
POSITION_MIN_PCT_DEFAULT |
30 |
Block SELL when ETH \x3C this % |
POSITION_MAX_PCT_BULLISH |
80 |
Allow more ETH in bullish trend |
POSITION_MIN_PCT_BEARISH |
25 |
Allow less ETH in bearish trend |
Additional safety guards:
- Rapid drop protection: skip BUY if price dropped >2% in last 30min (6 ticks)
- Consecutive same-direction reset: limit resets if grid was recalibrated or >1h since last trade
- Anti-repeat: skip if same direction + same level boundary as last trade
- Trend-adaptive position limits: limits shift based on trend direction and strength
Stop-Loss, Trailing Stop & Take-Profit
Three protection mechanisms. When triggered, trading halts and a red Discord alert is sent. Use resume-trading command to clear.
| Parameter | Default | Description |
|---|---|---|
STOP_LOSS_PCT |
0.15 |
Stop if portfolio drops 15% below cost basis |
TRAILING_STOP_PCT |
0.10 |
Stop if portfolio drops 10% from peak |
def _check_stop_conditions(state, total_usd, price):
cost_basis = initial + deposits
pnl_pct = (total_usd - cost_basis) / cost_basis
peak = max(stats["portfolio_peak_usd"], total_usd)
# Check: stop_loss, trailing_stop, take_profit
Risk Control Flow in tick()
1. If stop_triggered is set → log + Discord red alert + refuse trading + return
2. Check _check_stop_conditions → if triggered, set stop_triggered + alert + return
3. Multi-timeframe analysis → get trend context
4. Normal grid logic (cooldown, trend-adaptive position limits, etc.)
5. If SELL in strong uptrend → _should_delay_sell() check
Core Algorithm
1. Fetch token price
2. Read on-chain balances (ETH + USDC)
3. Multi-timeframe analysis → trend/strength/momentum/structure
4. Fetch K-line ATR volatility (hourly cache)
5. Check if grid needs recalibration (breakout / vol shift / age)
→ calc_dynamic_grid() uses trend-adaptive volatility multiplier
→ asymmetric buy_step/sell_step based on trend direction
6. Map price → grid level
7. If level changed:
a. Direction: BUY if level dropped, SELL if rose
b. If SELL in strong uptrend → delay check (trailing + momentum protection)
c. Safety checks (cooldown, trend-adaptive position limits, repeat guard, consecutive limit)
d. Calculate trade size (trend-adaptive sizing)
e. Execute swap via DEX aggregator
f. Record trade, update level ONLY on success
8. Calculate HODL Alpha
9. Report status (JSON + Discord)
Grid Calculation
def calc_dynamic_grid(price, price_history, mtf=None):
center = EMA(1H_kline, EMA_PERIOD) # 20-hour EMA on 1H candles
atr = calc_kline_volatility(candles)
# Trend-adaptive multiplier
vol_mult = VOLATILITY_MULTIPLIER_BASE # 2.0
if mtf and mtf["strength"] > 0.3:
vol_mult = blend(BASE=2.0, TREND=3.0, factor=strength)
# Asymmetric buy/sell multipliers
asym = ASYM_FACTOR * strength if strength > 0.3 else 0
if bullish:
buy_mult = vol_mult * (1 - asym) # tighter buy grid
sell_mult = vol_mult * (1 + asym) # wider sell grid
elif bearish:
buy_mult = vol_mult * (1 + asym) # wider buy grid
sell_mult = vol_mult * (1 - asym) # tighter sell grid
buy_step = clamp((buy_mult * atr) / half, floor, ceil)
sell_step = clamp((sell_mult * atr) / half, floor, ceil)
# Build asymmetric level_prices via _build_level_prices()
# Below center: spaced by buy_step; Above center: spaced by sell_step
level_prices = _build_level_prices(center, buy_step, sell_step, half, grid_type)
return {center, step, buy_step, sell_step, levels, range, vol_pct, type, level_prices}
Examples (at price $2000, ATR=$50):
| Trend | Strength | buy_step | sell_step | Buy Range | Sell Range | Behavior |
|---|---|---|---|---|---|---|
| Neutral | 0.1 | $33 | $33 | $1901-$2000 | $2000-$2099 | Symmetric, normal |
| Bullish | 0.6 | $33 | $54 | $1901-$2000 | $2000-$2162 | Buy dense + sell wide |
| Bearish | 0.6 | $54 | $33 | $1838-$2000 | $2000-$2099 | Buy wide + sell dense |
| Strong Bull | 0.9 | $24 | $66 | $1928-$2000 | $2000-$2198 | Max asymmetry |
Sell Optimization Logic
def _should_delay_sell(state, current_level, prev_level, mtf, history):
"""Returns skip reason or None."""
# 1. Momentum protection: skip sell if 1h momentum > 0.5% in uptrend
if momentum_1h > SELL_MOMENTUM_THRESHOLD * 100:
if trend == "bullish" and structure == "uptrend":
return "trend_hold (momentum +X.X%)"
# 2. Trailing sell: wait SELL_TRAIL_TICKS (2) before executing
trail = state["sell_trail_counter"]
level_key = f"{prev_level}->{current_level}"
if trail[level_key] \x3C SELL_TRAIL_TICKS:
trail[level_key] += 1
return "sell_trail (N/2)"
# 3. Cleared → proceed with sell
return None
Trend-Adaptive Position Limits
def _get_position_limits(mtf):
"""Return (max_pct, min_pct) based on trend."""
if trend == "bullish" and strength > 0.3:
max_pct = 70 + (80 - 70) * strength # allow more ETH
min_pct = 30
elif trend == "bearish" and strength > 0.3:
max_pct = 70
min_pct = 30 - (30 - 25) * strength # allow less ETH
else:
max_pct, min_pct = 70, 30
Trade Size
Returns (amount_in_smallest_unit, failure_info). SELL returns wei (x1e18), BUY returns uUSDC (x1e6).
def calc_trade_amount(direction, eth_bal, usdc_bal, price,
current_level=None, grid_levels=None,
mtf=None):
available_eth = eth_bal - GAS_RESERVE_ETH
total_usd = available_eth * price + usdc_bal
max_usd = total_usd * MAX_TRADE_PCT
# Apply trend-adaptive sizing
multiplier = _calc_sizing_multiplier(level, grid_levels, direction, mtf)
max_usd *= multiplier
if direction == "SELL":
return int(min(max_usd / price, available_eth) * 1e18), None # wei
else:
return int(min(max_usd, usdc_bal * 0.95) * 1e6), None # uUSDC
Level Update Rule (Critical)
| Outcome | Update level? | Rationale |
|---|---|---|
| Trade succeeded | Yes | Grid crossing consumed |
| Trade failed | No | Retry on next tick |
| Trade skipped (cooldown/limit) | No | Don't lose the crossing |
| Sell delayed (trailing/momentum) | No | Will retry next tick |
PnL Tracking (dual-denominated)
# USD-denominated
total_pnl_usd = current_portfolio_usd - cost_basis
hodl_value_usd = initial_eth_amount × current_price
grid_alpha_usd = current_portfolio_usd - hodl_value_usd
# ETH-denominated
current_eth_equivalent = current_portfolio_usd / current_price
initial_eth_equivalent = cost_basis / initial_price
total_pnl_eth = current_eth_equivalent - initial_eth_equivalent
HODL Alpha (key metric): grid_alpha_usd > 0 means grid outperforms pure ETH holding.
State Schema
{
"version": 5,
"grid": {"center": 2000, "step": 43.5, "buy_step": 33.3, "sell_step": 53.7,
"levels": 6, "range": [1900, 2161], "vol_pct": 2.1,
"type": "arithmetic",
"level_prices": [1900, 1933, 1967, 2000, 2054, 2107, 2161]},
"grid_set_at": "ISO timestamp",
"current_level": 3,
"price_history": ["...max 288 (24h at 5min)"],
"trades": [{"time": "...", "direction": "SELL", "price": 2050,
"amount_usd": 25, "est_profit": 1.5, "tx": "0x...",
"grid_from": 2, "grid_to": 3}],
"stats": {
"total_trades": 15,
"realized_pnl": 5.2,
"grid_profit": 3.8,
"initial_portfolio_usd": 1000,
"initial_eth_price": 2000.0,
"portfolio_peak_usd": 1050.0,
"total_deposits_usd": 0.0,
"deposit_history": [],
"trade_attempts": 10, "trade_successes": 10, "trade_failures": 0,
"sell_attempts": 5, "sell_successes": 5,
"buy_attempts": 5, "buy_successes": 5,
"retry_attempts": 0, "retry_successes": 0,
"started_at": "ISO timestamp",
"last_check": "ISO timestamp"
},
"stop_triggered": null,
"last_trade_times": {"BUY": "...", "SELL": "..."},
"last_failed_trade": null,
"last_balances": {"eth": 0.134, "usdc": 257.33, "time": "ISO timestamp"},
"last_quiet_report": "ISO timestamp",
"upside_breakout_ticks": 0,
"approved_routers": ["0x..."],
"errors": {"consecutive": 0, "cooldown_until": null},
"mtf_cache": null,
"kline_cache": null,
"sell_trail_counter": {}
}
Key fields:
grid.type+grid.level_prices: geometric/arithmetic grid support (asymmetric spacing)grid.buy_step/grid.sell_step: directional step sizes;grid.step= average for backward compatstats.initial_eth_price: records ETH price at bot start for HODL Alpha calculationstats.portfolio_peak_usd: highest portfolio value (for trailing stop)stop_triggered: string describing trigger condition, or nulllast_failed_trade: cached forretrycommand (expires after 10min)upside_breakout_ticks: confirmation counter for upside recalibrationapproved_routers: USDC approval cache to avoid redundant approvalsmtf_cache: cached multi-timeframe analysis resultkline_cache: cached K-line data (1h TTL)sell_trail_counter: tracks sell delay tick counts per level transition
Operational Interface
Sub-Commands
| Command | Purpose | Trigger |
|---|---|---|
tick |
Main loop: price → MTF → grid → trade → report | Cron every 5min |
status |
Print current grid state, balances, PnL, trend | On demand |
report |
Generate daily performance report (Chinese) | Cron daily 08:00 CST |
history |
Show recent trade history | On demand |
reset |
Reset grid (recalibrate from scratch), keep trade history | Manual |
retry |
Retry last failed trade with fresh quote (expires after 10min) | AI agent / manual |
analyze |
Output detailed market + MTF + round-trip analysis JSON | AI agent |
deposit |
Manually record deposit/withdrawal for PnL tracking | Manual |
resume-trading |
Clear stop_triggered flag and resume trading | Manual / AI agent |
COMMANDS = {
"tick": tick, "status": status, "report": report,
"history": history_cmd, "reset": reset, "retry": retry,
"analyze": analyze, "deposit": deposit,
"resume-trading": resume_trading
}
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "tick"
COMMANDS.get(cmd, tick)()
AI Agent Output Protocol
The tick command outputs a structured JSON block for AI agent parsing:
---JSON---
{
"version": "1.0",
"status": "trade_executed" | "no_trade" | "cooldown" | "trade_failed" | ...,
"market": {
"price": 2090.45, "ema": 2085.3, "volatility_pct": 1.2,
"trend": "bullish", "trend_strength": 0.65,
"momentum_1h": 0.35, "momentum_4h": 1.2,
"structure": "uptrend",
"kline_atr_pct": 1.8
},
"portfolio": {"eth": 0.134, "usdc": 257.33, "total_usd": 538.0, "eth_pct": 52.1},
"grid_level": 3,
"direction": "SELL",
"tx_hash": "0x...",
"failure_reason": "...",
"retriable": true,
"hodl_alpha": 2.15,
"success_rate": {"total_attempts": 182, "successes": 182, "rate_pct": 100.0}
}
The analyze command outputs additional fields:
multi_timeframe: full MTF data (EMA short/medium/long, momentum, structure)round_trips: trade pair analysis (good / micro / loss classification)
Discord Notification
Two card formats pushed via Discord Bot API:
Trade executed (colored embed):
- Green = SELL, Blue = BUY
- Fields: price, level, total value, position, PnL, grid profit, HODL Alpha, BaseScan link
No trade (grey compact card):
- One-line: price . level . position . PnL . trend . trade count
- Only sent once per
QUIET_INTERVAL(default 1 hour)
Deposit/Withdrawal Detection
Automatically detects external balance changes:
unexplained_change = (current_balance - last_balance) - sum(recorded_trades_since_last)
if abs(unexplained_change) > $100:
record as deposit or withdrawal → adjust PnL cost basis
Logging
- File:
grid_bot.login script directory - Rotation: simple half-file rotation at 1MB
- Format:
[YYYY-MM-DD HH:MM:SS] message
Adapting to Different Pairs
| Consideration | What to adjust |
|---|---|
| Token decimals | USDC=6, DAI=18, WBTC=8 — affects amount conversion |
| Typical volatility | BTC lower vol → smaller STEP_MIN/MAX_PCT; meme coins → larger |
| Liquidity depth | Low liquidity → smaller MAX_TRADE_PCT, add price impact check |
| Gas costs | L1 vs L2: adjust GAS_RESERVE and MIN_TRADE_USD |
| Stablecoin pair | TOKEN/USDC pair: STEP_MIN_PCT can be much tighter (0.2%) |
| Rate limits | Add 300-500ms delay between consecutive OKX API calls |
AI Review & Optimization
AI agent should periodically review trading performance and suggest/apply optimizations. Run weekly or when cumulative PnL stalls.
Step 1: Pull & Pair Trades
Extract recent trades and pair each BUY with its corresponding SELL to form round trips.
# Matching logic: SELL from level A→B matches BUY from level B→A
buy_stack = []
round_trips = []
for trade in trades:
if trade["direction"] == "BUY":
buy_stack.append(trade)
else: # SELL
for j in range(len(buy_stack)-1, -1, -1):
if buy_stack[j]["grid_to"] == trade["grid_from"]:
matched_buy = buy_stack.pop(j)
round_trips.append((matched_buy, trade))
break
Output per round trip:
- Spread:
(sell_price - buy_price) / buy_price x 100% - Hold time: minutes between buy and sell
- Status: profit (spread > 0.3%), micro-profit (0 \x3C spread \x3C 0.3%), loss (spread \x3C 0)
Step 2: Flag Anomalies
| Flag | Condition | Meaning |
|---|---|---|
LOSS |
spread \x3C 0 | Bought high, sold low |
MICRO |
0 \x3C spread \x3C 0.3% | Profit too small to cover DEX costs |
GOOD |
spread >= 0.3% | Healthy grid profit |
Key metrics: win rate, loss impact, micro-trade ratio (if > 30%, step too small).
Step 3: Root Cause Analysis
LOSS trades:
| Pattern | Root Cause | Fix |
|---|---|---|
| Buy @high, sell @low after recalibration | Grid chased a spike | Increase UPSIDE_CONFIRM_TICKS, reduce MAX_CENTER_SHIFT_PCT |
| Buy @high in trend, sell @low on reversal | EMA too reactive | Increase EMA_PERIOD or GRID_RECALIBRATE_HOURS |
| Loss during flash crash | Stop-loss too loose | Tighten STOP_LOSS_PCT |
MICRO trades:
| Pattern | Root Cause | Fix |
|---|---|---|
| Many trades with \x3C 0.2% spread | Step too small | Increase STEP_MIN_PCT |
| Rapid back-and-forth at same levels | Low vol, grid too dense | Increase MIN_TRADE_INTERVAL |
| Trades cluster in 5-10 min windows | Cooldown too short | Increase MIN_TRADE_INTERVAL |
Step 4: Parameter Tuning
STEP_MIN_PCT >= DEX_total_cost x 3
where DEX_total_cost ~ slippage + price_impact ~ 0.1-0.3% on L2
→ STEP_MIN_PCT >= 0.009 to 0.012
UPSIDE_CONFIRM_TICKS = typical_spike_duration / tick_interval
e.g., spikes last ~20min, tick=5min → confirm_ticks = 4-6
MAX_CENTER_SHIFT_PCT = step_pct x 2-3
Step 5: Backtest & Apply
Simulate new parameters against historical data, then: backup → patch → recalibrate → monitor 24h → re-run analysis.
Review Checklist (AI Agent Prompt)
1. Read grid_state.json and grid_bot.log
2. Filter trades to review window (default: last 48h)
3. Pair trades into round trips
4. Compute: win_rate, avg_spread, loss_count, micro_count, total_pnl, hodl_alpha
5. If loss_count > 0: trace each loss to recalibration events
6. If micro_ratio > 30%: recommend STEP_MIN_PCT increase
7. Check MTF data for trend alignment during losses
8. Propose specific parameter changes with backtest evidence
9. On user approval: backup → patch → recalibrate → verify
Failure & Rollback
IF Step N fails:
1. Log failure reason to grid_bot.log
2. Increment errors.consecutive
3. If errors.consecutive >= 5: trigger circuit breaker (1h cooldown)
4. Cache failed trade for retry command (10min expiry)
5. DO NOT update grid level
6. Report failure via Discord + JSON output
Anti-Patterns
| Pattern | Problem |
|---|---|
| Recalibrate every tick | Grid oscillates, no stable levels |
| Update level on failure/skip | Silently loses grid crossings |
| No position limits | Trending market → 100% one-sided |
| Fixed step in volatile market | Too small → over-trades; too large → never triggers |
sell - buy as PnL |
Net cash flow ≠ profit |
| No cooldown | Rapid swings cause burst of trades eating slippage |
| No stop-loss | Single crash wipes out months of grid profits |
| Martingale without cap | Exponential position growth → liquidation risk |
| Arithmetic grid on wide range | $20 step meaningless at $5000 but huge at $500 |
| Symmetric recalibration | Chasing upside spikes = buying high then selling low on reversal |
| Step floor too low | Micro-profit trades only feed DEX fees, net negative after costs |
| No center shift cap | Single spike can drag grid center 5%+, creating losing positions |
| Fixed sizing in trends | Selling same size in uptrend = giving away alpha to the market |
| Selling immediately in uptrend | Sell delay exists for a reason — let trends play out |
| Symmetric grid in strong trends | Asymmetric grids accumulate faster on the favorable side |
Ignoring buy_step/sell_step in profit calc |
Use actual level_prices differences, not average step |
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install grid-trading - After installation, invoke the skill by name or use
/grid-trading - Provide required inputs per the skill's parameter spec and get structured output
What is Grid Trading?
Dynamic grid trading strategy for any token pair on EVM L2 chains via OKX DEX API. Features asymmetric grid steps (buy-dense/sell-wide in bullish, reverse in... It is an AI Agent Skill for Claude Code / OpenClaw, with 303 downloads so far.
How do I install Grid Trading?
Run "/install grid-trading" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Grid Trading free?
Yes, Grid Trading is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Grid Trading support?
Grid Trading is cross-platform and runs anywhere OpenClaw / Claude Code is available (darwin, linux).
Who created Grid Trading?
It is built and maintained by SynthThoughts (@synththoughts); the current version is v1.5.3.