Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when writing a profit-laddered position-adjustment strategy on Superior Trade — anything described as a grid bot, range fade, range harvest, ladder buy, scaling-in, pyramiding, or "buy more when it dips and sell partials when it rallies". Note this is a profit-driven ladder, not a true 20-rung order-book grid; explain that limitation when the user asks for true grid trading.
.claude/skills/superior-trade-grid-trading/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-23 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-01 | ✗→✓ | ▲ Improved | -28% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -22% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 41% | 0% |
A user asks for "grid trading", "grid bot", "range fade", "ladder buy", "scale into the dip", "pyramid into a position", "DCA on drawdown" (not on calendar — that's strategy-dca-weekly). Anything where the trigger to add is a price drawdown, and there are partial take-profits on the way up.
Freqtrade is a one-trade-per-pair engine. A real 20-rung grid bot — placing 20 limit orders simultaneously on the order book and refilling each as it fills — is not possible without engine changes. What you can implement is a profit-laddered position adjustment:
This is a working, profitable approximation of the spirit of grid trading. If the user explicitly wants 100s of small fills per day on a tight book, say so and recommend running a separate grid runtime alongside Freqtrade.
| Window | ETH/USDC 15m, 2026-03-01 → 2026-05-01 (61 days) | |---|---| | Trades | 4 | | Win rate | 100% | | Wallet PnL | +0.66% / +$65.58 | | Sharpe | 2.02 | | Profit per trade | $15-30 | | Avg holding | 14 days | | Max DD | 0% (intraday only) | | Backtest ID | 01kqyz25d0zrwwf5fzccjk44dk |
Order pattern per trade: 2 entries ("" initial + grid_buy_1) + 4 partial exits at grid_tp_* tags. Sparse — 4 trades over 61 days — because the 24h VWAP −1% trigger fires rarely on ETH. Tighten the trigger (e.g. vwap × 0.995) for more activity.
pythonfrom freqtrade.strategy import IStrategy from freqtrade.persistence import Trade from datetime import datetime import pandas as pd class EthGridStrategy(IStrategy): minimal_roi = {"0": 100.0} # never auto-close on ROI; partials handled in adjust_trade_position stoploss = -0.30 # safety net, deeper than the deepest ladder rung trailing_stop = False timeframe = "15m" process_only_new_candles = True startup_candle_count = 200 can_short = False position_adjustment_enable = True max_entry_position_adjustment = 5 # 5 ladder rungs below entry max_dca_multiplier = 6.0 # 1 + 5 adds def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # 24h VWAP on 15m bars (96 bars). tp = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3.0 pv = tp * dataframe["volume"] dataframe["vwap_24h"] = ( pv.rolling(96).sum() / dataframe["volume"].rolling(96).sum() ) return dataframe def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # First grid rung: 1% below 24h VWAP. dataframe.loc[ (dataframe["close"] <= dataframe["vwap_24h"] * 0.99) & (dataframe["volume"] > 0), "enter_long", ] = 1 return dataframe def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # Hard close on band breakout up. dataframe.loc[ dataframe["close"] >= dataframe["vwap_24h"] * 1.06, "exit_long", ] = 1 return dataframe def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake, max_stake: float, leverage: float, entry_tag, side: str, **kwargs) -> float: return proposed_stake / self.max_dca_multiplier def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake, max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, **kwargs): if trade.has_open_orders: return None n_entries = trade.nr_of_successful_entries n_exits = trade.nr_of_successful_exits # Ladder buys: every -1% from average entry, up to 5 adds. if n_entries <= 5 and current_profit <= -0.01 * n_entries: filled = trade.select_filled_orders(trade.entry_side) first_stake = filled[0].stake_amount_filled if filled else (min_stake or 10) return (first_stake, f"grid_buy_{n_entries}") # Partial profit-take: every +1.5% above avg entry, up to 3 ladders. if n_exits < 3 and current_profit >= 0.015 * (n_exits + 1): return (-(trade.stake_amount / 4.0), f"grid_tp_{n_exits}") return None
json{ "exchange": { "name": "hyperliquid", "pair_whitelist": ["ETH/USDC"] }, "stake_currency": "USDC", "stake_amount": 1000, "dry_run_wallet": {"USDC": 10000}, "timeframe": "15m", "max_open_trades": 1, "stoploss": -0.30, "minimal_roi": { "0": 100.0 }, "entry_pricing": { "price_side": "same" }, "exit_pricing": { "price_side": "same" }, "pairlists": [{ "method": "StaticPairList" }] }
dry_run_wallet ≥ stake_amount is enforced strictly. With 6 ladder rungs, leave headroom — dry_run_wallet ≥ stake_amount × 1.5 is comfortable.
| Knob | Effect | |---|---| | 0.99 (entry trigger) | Tighter (0.995) → more entries, more chop. Looser (0.97) → rarer, deeper fades. | | 0.01 * n_entries (ladder spacing) | Tighter spacing → faster ladder fills, smaller gain per rung. Wider spacing → fewer rungs in chop. | | max_entry_position_adjustment | More rungs → bigger position when fully laddered, more wallet exposure. | | 0.015 * (n_exits + 1) (TP step) | Tighter TPs → more partial closes, less per close. | | 1.06 (band breakout) | Tighter (1.04) → exit earlier on rallies, capture less. | | trade.stake_amount / 4.0 (TP size) | Smaller divisor → bigger partial closes. / 2.0 halves the position per TP. |
populate_entry_trend with close < vwap × 0.94 and populate_exit_trend with close > vwap × 1.06 produced 0 trades on the same window — ETH never reached the lower band. The laddered version captures the moves the band misses.stoploss too shallow. With 5 ladder rungs at −1% spacing, a −6% stop kills the trade before the deepest rung fills. Use −30% (or deeper) and rely on partial exits.minimal_roi close trades early. With the default {"0": 0.02}, the trade exits at +2% before the partial-TP ladder ever runs. Set {"0": 100.0} to disable.current_profit is signed. current_profit <= -0.01 * n_entries reads "drawdown is at least n × 1%". Inverting the sign disables the ladder.0.97 entry / 1.10 exit for trending pairs (BTC, SOL).max_entry_position_adjustment = 8, only 2 partial TPs) for accumulation modes.0.01 with atr_pct * 0.5 to make ladder spacing follow regime.fees-optimizations for cost analysis.adjust_trade_position — https://www.freqtrade.io/en/stable/strategy-callbacks/#adjust-trade-positiondocs/standard-strategies-audit.md, backtest 01kqyz25d0zrwwf5fzccjk44dk| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 18,219 | 43,042 | +136% | 1 | 1 | 0% | 3,643 | 8,755 | +140% | 0 | 0 | — |
case-23 | fail→pass | 20,124 | 13,125 | -35% | 1 | 1 | 0% | 3,055 | 4,415 | +45% | 0 | 0 | — |
case-01 | fail→pass | 50,198 | 22,530 | -55% | 1 | 1 | 0% | 8,260 | 5,967 | -28% | 0 | 0 | — |
case-02 | fail→pass | 32,030 | 19,191 | -40% | 1 | 1 | 0% | 4,843 | 6,181 | +28% | 0 | 0 | — |
case-03 | fail→pass | 113,440 | 18,185 | -84% | 1 | 1 | 0% | 7,962 | 6,177 | -22% | 0 | 0 | — |
case-04 | pass→fail | 27,076 | 23,157 | -14% | 1 | 1 | 0% | 4,066 | 7,109 | +75% | 0 | 0 | — |
case-06 | pass→pass | 99,820 | 25,520 | -74% | 1 | 1 | 0% | 5,395 | 6,355 | +18% | 0 | 0 | — |
case-07 | fail→pass | 27,979 | 14,428 | -48% | 1 | 1 | 0% | 3,535 | 4,996 | +41% | 0 | 0 | — |
case-08 | pass→pass | 17,243 | 10,448 | -39% | 1 | 1 | 0% | 2,209 | 4,276 | +94% | 0 | 0 | — |
case-09 | fail→pass | 21,266 | 9,384 | -56% | 1 | 1 | 0% | 3,111 | 3,710 | +19% | 0 | 0 | — |
case-10 | pass→pass | 10,745 | 7,177 | -33% | 1 | 1 | 0% | 1,410 | 3,382 | +140% | 0 | 0 | — |
case-11 | fail→pass | 17,385 | 7,722 | -56% | 1 | 1 | 0% | 3,123 | 3,459 | +11% | 0 | 0 | — |
case-12 | fail→pass | 15,993 | 6,297 | -61% | 1 | 1 | 0% | 2,343 | 3,452 | +47% | 0 | 0 | — |
case-13 | pass→pass | 15,548 | 9,745 | -37% | 1 | 1 | 0% | 2,318 | 3,726 | +61% | 0 | 0 | — |
case-14 | pass→pass | 20,464 | 15,436 | -25% | 1 | 1 | 0% | 2,635 | 4,572 | +74% | 0 | 0 | — |
case-15 | fail→pass | 12,656 | 5,383 | -57% | 1 | 1 | 0% | 2,203 | 3,324 | +51% | 0 | 0 | — |
case-16 | fail→pass | 13,402 | 4,123 | -69% | 1 | 1 | 0% | 1,905 | 3,058 | +61% | 0 | 0 | — |
case-17 | pass→pass | 11,496 | 14,303 | +24% | 1 | 1 | 0% | 1,588 | 4,518 | +185% | 0 | 0 | — |
case-18 | fail→pass | 17,162 | 7,221 | -58% | 1 | 1 | 0% | 3,048 | 3,473 | +14% | 0 | 0 | — |
case-19 | fail→pass | 25,999 | 19,925 | -23% | 1 | 1 | 0% | 3,456 | 5,984 | +73% | 0 | 0 | — |
case-20 | fail→fail | 21,629 | 22,336 | +3% | 1 | 1 | 0% | 3,833 | 5,570 | +45% | 0 | 0 | — |
case-21 | pass→pass | 16,977 | 30,846 | +82% | 1 | 1 | 0% | 2,281 | 4,994 | +119% | 0 | 0 | — |
case-22 | fail→pass | 7,967 | 9,634 | +21% | 1 | 1 | 0% | 1,398 | 4,101 | +193% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +52 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.