Managing Multiple Price-Bound Grid Executors with Capital Allocations
Summary
This controller coordinates multiple configured grids for one trading pair. Each grid has a price range, limit price, side, enable flag, and share of total quote capital. The controller checks the mid-price and creates a grid executor when price is within that grid’s bounds; it stops executors associated with grids that are removed or disabled. It also passes order-frequency, order-count, spread, activation, leverage, and take-profit settings to the executor, and tracks activity for status reporting.
The design is an execution and allocation framework rather than a complete market-timing signal: users provide the grid boundaries and direction. The sample defaults target a WLD-USDT perpetual connector and use hedge mode, but these configuration values do not establish suitability or profitability. The excerpt provides no backtest or trading results. It also leaves important strategy choices to the operator, including how to select grid ranges, distribute capital safely across grids, and handle market moves outside those ranges. Leverage and the configured order behavior can materially affect risk.
Key ideas
- Each grid defines a price interval, trading side, limit price, enable state, and fraction of total quote capital.
- A grid executor is created when the mid-price lies inside an enabled grid’s range.
- Executors are stopped when their grids are removed or disabled after a configuration change.
- The controller allocates capital and passes order-management and take-profit settings to each executor.
- The document gives implementation details but no evidence of profitability or guidance for choosing grid boundaries.
Tags
Full text
# MultiGridStrike
# MultiGridStrike
Configuration for an individual grid
## Source (Apache-2.0)
```python
from decimal import Decimal
from typing import Dict, List, Optional
from pydantic import BaseModel, Field
from hummingbot.core.data_type.common import MarketDict, OrderType, PositionMode, PriceType, TradeType
from hummingbot.strategy_v2.controllers import ControllerBase, ControllerConfigBase
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
from hummingbot.strategy_v2.executors.grid_executor.data_types import GridExecutorConfig
from hummingbot.strategy_v2.executors.position_executor.data_types import TripleBarrierConfig
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction
from hummingbot.strategy_v2.models.executors_info import ExecutorInfo
class GridConfig(BaseModel):
"""Configuration for an individual grid"""
grid_id: str
start_price: Decimal = Field(json_schema_extra={"is_updatable": True})
end_price: Decimal = Field(json_schema_extra={"is_updatable": True})
limit_price: Decimal = Field(json_schema_extra={"is_updatable": True})
side: TradeType = Field(json_schema_extra={"is_updatable": True})
amount_quote_pct: Decimal = Field(json_schema_extra={"is_updatable": True}) # Percentage of total amount (0.0 to 1.0)
enabled: bool = Field(default=True, json_schema_extra={"is_updatable": True})
class MultiGridStrikeConfig(ControllerConfigBase):
"""
Configuration for MultiGridStrike strategy supporting multiple grids
"""
controller_type: str = "generic"
controller_name: str = "multi_grid_strike"
# Account configuration
leverage: int = 20
position_mode: PositionMode = PositionMode.HEDGE
# Common configuration
connector_name: str = "binance_perpetual"
trading_pair: str = "WLD-USDT"
# Total capital allocation
total_amount_quote: Decimal = Field(default=Decimal("1000"), json_schema_extra={"is_updatable": True})
# Grid configurations
grids: List[GridConfig] = Field(default_factory=list, json_schema_extra={"is_updatable": True})
# Common grid parameters
min_spread_between_orders: Optional[Decimal] = Field(default=Decimal("0.001"), json_schema_extra={"is_updatable": True})
min_order_amount_quote: Optional[Decimal] = Field(default=Decimal("5"), json_schema_extra={"is_updatable": True})
# Execution
max_open_orders: int = Field(default=2, json_schema_extra={"is_updatable": True})
max_orders_per_batch: Optional[int] = Field(default=1, json_schema_extra={"is_updatable": True})
order_frequency: int = Field(default=3, json_schema_extra={"is_updatable": True})
activation_bounds: Optional[Decimal] = Field(default=None, json_schema_extra={"is_updatable": True})
keep_position: bool = Field(default=False, json_schema_extra={"is_updatable": True})
# Risk Management
triple_barrier_config: TripleBarrierConfig = TripleBarrierConfig(
take_profit=Decimal("0.001"),
open_order_type=OrderType.LIMIT_MAKER,
take_profit_order_type=OrderType.LIMIT_MAKER,
)
def update_markets(self, markets: MarketDict) -> MarketDict:
return markets.add_or_update(self.connector_name, self.trading_pair)
class MultiGridStrike(ControllerBase):
def __init__(self, config: MultiGridStrikeConfig, *args, **kwargs):
super().__init__(config, *args, **kwargs)
self.config = config
self._last_config_hash = self._get_config_hash()
self._grid_executor_mapping: Dict[str, str] = {} # grid_id -> executor_id
self.trading_rules = None
self.initialize_rate_sources()
def initialize_rate_sources(self):
self.market_data_provider.initialize_rate_sources([ConnectorPair(connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair)])
def _get_config_hash(self) -> str:
"""Generate a hash of the current grid configurations"""
return str(hash(tuple(
(g.grid_id, g.start_price, g.end_price, g.limit_price, g.side, g.amount_quote_pct, g.enabled)
for g in self.config.grids
)))
def _has_config_changed(self) -> bool:
"""Check if configuration has changed"""
current_hash = self._get_config_hash()
changed = current_hash != self._last_config_hash
if changed:
self._last_config_hash = current_hash
return changed
def active_executors(self) -> List[ExecutorInfo]:
return [
executor for executor in self.executors_info
if executor.is_active
]
def get_executor_by_grid_id(self, grid_id: str) -> Optional[ExecutorInfo]:
"""Get executor associated with a specific grid"""
executor_id = self._grid_executor_mapping.get(grid_id)
if executor_id:
for executor in self.executors_info:
if executor.id == executor_id:
return executor
return None
def calculate_grid_amount(self, grid: GridConfig) -> Decimal:
"""Calculate the actual amount for a grid based on its percentage allocation"""
return self.config.total_amount_quote * grid.amount_quote_pct
def is_inside_bounds(self, price: Decimal, grid: GridConfig) -> bool:
"""Check if price is within grid bounds"""
return grid.start_price <= price <= grid.end_price
def determine_executor_actions(self) -> List[ExecutorAction]:
actions = []
mid_price = self.market_data_provider.get_price_by_type(
self.config.connector_name, self.config.trading_pair, PriceType.MidPrice)
# Check for config changes
if self._has_config_changed():
# Handle removed or disabled grids
current_grid_ids = {g.grid_id for g in self.config.grids if g.enabled}
for grid_id, executor_id in list(self._grid_executor_mapping.items()):
if grid_id not in current_grid_ids:
# Stop executor for removed/disabled grid
actions.append(StopExecutorAction(
controller_id=self.config.id,
executor_id=executor_id
))
del self._grid_executor_mapping[grid_id]
# Process each enabled grid
for grid in self.config.grids:
if not grid.enabled:
continue
executor = self.get_executor_by_grid_id(grid.grid_id)
# Create new executor if none exists and price is in bounds
if executor is None and self.is_inside_bounds(mid_price, grid):
executor_action = CreateExecutorAction(
controller_id=self.config.id,
executor_config=GridExecutorConfig(
timestamp=self.market_data_provider.time(),
connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair,
start_price=grid.start_price,
end_price=grid.end_price,
leverage=self.config.leverage,
limit_price=grid.limit_price,
side=grid.side,
total_amount_quote=self.calculate_grid_amount(grid),
min_spread_between_orders=self.config.min_spread_between_orders,
min_order_amount_quote=self.config.min_order_amount_quote,
max_open_orders=self.config.max_open_orders,
max_orders_per_batch=self.config.max_orders_per_batch,
order_frequency=self.config.order_frequency,
activation_bounds=self.config.activation_bounds,
triple_barrier_config=self.config.triple_barrier_config,
level_id=grid.grid_id, # Use grid_id as level_id for identification
keep_position=self.config.keep_position,
))
actions.append(executor_action)
# Note: We'll update the mapping after executor is created
# Update executor mapping if needed
if executor is None and len(actions) > 0:
# This will be handled in the next cycle after executor is created
pass
return actions
async def update_processed_data(self):
# Update executor mapping for newly created executors
for executor in self.active_executors():
if hasattr(executor.config, 'level_id') and executor.config.level_id:
self._grid_executor_mapping[executor.config.level_id] = executor.id
def to_format_status(self) -> List[str]:
status = []
mid_price = self.market_data_provider.get_price_by_type(
self.config.connector_name, self.config.trading_pair, PriceType.MidPrice)
# Define standard box width for consistency
box_width = 114
# Top Multi-Grid Configuration box
status.append("┌" + "─" * box_width + "┐")
# Header
header = f"│ Multi-Grid Configuration - {self.config.connector_name} {self.config.trading_pair}"
header += " " * (box_width - len(header) + 1) + "│"
status.append(header)
# Mid price, grid count, and total amount
active_grids = len([g for g in self.config.grids if g.enabled])
total_grids = len(self.config.grids)
total_amount = self.config.total_amount_quote
info_line = f"│ Mid Price: {mid_price:.4f} │ Active Grids: {active_grids}/{total_grids} │ Total Amount: {total_amount:.2f} │"
info_line += " " * (box_width - len(info_line) + 1) + "│"
status.append(info_line)
status.append("└" + "─" * box_width + "┘")
# Display each grid configuration
for grid in self.config.grids:
if not grid.enabled:
continue
executor = self.get_executor_by_grid_id(grid.grid_id)
in_bounds = self.is_inside_bounds(mid_price, grid)
# Grid header
grid_status = "ACTIVE" if executor else ("READY" if in_bounds else "OUT_OF_BOUNDS")
status_header = f"Grid {grid.grid_id}: {grid_status}"
status_line = f"┌ {status_header}" + "─" * (box_width - len(status_header) - 2) + "┐"
status.append(status_line)
# Grid configuration
grid_amount = self.calculate_grid_amount(grid)
pct_display = f"{grid.amount_quote_pct * 100:.1f}%"
config_line = f"│ Start: {grid.start_price:.4f} │ End: {grid.end_price:.4f} │ Side: {grid.side} │ Limit: {grid.limit_price:.4f} │ Amount: {grid_amount:.2f} ({pct_display}) │"
config_line += " " * (box_width - len(config_line) + 1) + "│"
status.append(config_line)
if executor:
# Display executor statistics
col_width = box_width // 3
# Column headers
header_line = "│ Level Distribution" + " " * (col_width - 20) + "│"
header_line += " Order Statistics" + " " * (col_width - 18) + "│"
header_line += " Performance Metrics" + " " * (col_width - 21) + "│"
status.append(header_line)
# Data columns
level_dist_data = [
f"NOT_ACTIVE: {executor.custom_info.get('levels_by_state', {}).get('NOT_ACTIVE', 0)}",
f"OPEN_ORDER_PLACED: {executor.custom_info.get('levels_by_state', {}).get('OPEN_ORDER_PLACED', 0)}",
f"OPEN_ORDER_FILLED: {executor.custom_info.get('levels_by_state', {}).get('OPEN_ORDER_FILLED', 0)}",
f"CLOSE_ORDER_PLACED: {executor.custom_info.get('levels_by_state', {}).get('CLOSE_ORDER_PLACED', 0)}",
f"COMPLETE: {executor.custom_info.get('levels_by_state', {}).get('COMPLETE', 0)}"
]
order_stats_data = [
f"Total: {sum(len(executor.custom_info.get(k, [])) for k in ['filled_orders', 'failed_orders', 'canceled_orders'])}",
f"Filled: {len(executor.custom_info.get('filled_orders', []))}",
f"Failed: {len(executor.custom_info.get('failed_orders', []))}",
f"Canceled: {len(executor.custom_info.get('canceled_orders', []))}"
]
perf_metrics_data = [
f"Buy Vol: {executor.custom_info.get('realized_buy_size_quote', 0):.4f}",
f"Sell Vol: {executor.custom_info.get('realized_sell_size_quote', 0):.4f}",
f"R. PnL: {executor.custom_info.get('realized_pnl_quote', 0):.4f}",
f"R. Fees: {executor.custom_info.get('realized_fees_quote', 0):.4f}",
f"P. PnL: {executor.custom_info.get('position_pnl_quote', 0):.4f}",
f"Position: {executor.custom_info.get('position_size_quote', 0):.4f}"
]
# Build rows
max_rows = max(len(level_dist_data), len(order_stats_data), len(perf_metrics_data))
for i in range(max_rows):
col1 = level_dist_data[i] if i < len(level_dist_data) else ""
col2 = order_stats_data[i] if i < len(order_stats_data) else ""
col3 = perf_metrics_data[i] if i < len(perf_metrics_data) else ""
row = "│ " + col1
row += " " * (col_width - len(col1) - 2)
row += "│ " + col2
row += " " * (col_width - len(col2) - 2)
row += "│ " + col3
row += " " * (col_width - len(col3) - 2)
row += "│"
status.append(row)
# Liquidity line
status.append("├" + "─" * box_width + "┤")
liquidity_line = f"│ Open Liquidity: {executor.custom_info.get('open_liquidity_placed', 0):.4f} │ Close Liquidity: {executor.custom_info.get('close_liquidity_placed', 0):.4f} │"
liquidity_line += " " * (box_width - len(liquidity_line) + 1) + "│"
status.append(liquidity_line)
status.append("└" + "─" * box_width + "┘")
return status
```Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.