"""Strategy cookbook: a grid. A drop-in strategy.py for the Orblivion starter bot. It lays price levels STEP apart around a centre: LEVELS above it and LEVELS below. Each time ETH falls through a level it buys ORDER_USD of ETH with USDG; each time it rises through one it sells ORDER_USD of ETH. It remembers the level it last traded at (in state), so a sale needs ETH to climb a whole step above the last buy, and a price that wobbles around one level doesn't trade on every tick. A jump through several levels in one tick trades them all in one order. Under the lowest level it buys no more, and over the highest it sells no more. To use it, copy this file over the starter bot's strategy.py and keep dry_run = true in config.toml until the log shows what you expect. decide(market, book, state) gets what the starter bot's strategy.py gets (prices, the wallet's balances, and state kept in bot-state.json) and returns a list of Order. bot.py cuts every order to your limits. The grid follows the price, not the wallet: when the wallet can't pay for a level, bot.py skips the order (and logs it), and the grid still moves to that level. A wallet with both ETH and USDG can trade both ways. An example to learn from, not advice: a price that leaves the grid leaves it holding all ETH (below) or none (above). """ from dataclasses import dataclass @dataclass class Order: sell: str # the token to sell, e.g. "ETH" buy: str # the token to buy, e.g. "USDG" amount_usd: float # how many dollars of `sell` to trade reason: str = "" # why: shown in the console and bot-log.jsonl CENTRE = 0 # the grid's middle, in dollars per ETH; 0: the ETH price on the grid's first tick STEP = 0.02 # levels 2% of the centre apart: wider steps trade less often LEVELS = 3 # levels on each side of the centre ORDER_USD = 5 # dollars traded at each level def decide(market, book, state): s = state.setdefault("grid", {}) # this strategy's own corner of state price = market.get("ETH") if price is None: return [] # no ETH price this tick: wait for the next one if "centre" not in s or (CENTRE and s["centre"] != CENTRE): s.update(centre=CENTRE or price, level=0) # a new grid (or CENTRE changed): start at its middle centre, start = s["centre"], s["level"] level = start while level > -LEVELS and price <= centre * (1 + (level - 1) * STEP): level -= 1 # down through a level: buy while level < LEVELS and price >= centre * (1 + (level + 1) * STEP): level += 1 # up through a level: sell s["level"] = level moved = level - start if moved == 0: return [] why = f"ETH ${price:,.2f}: from level {start} to {level} of a grid {STEP:.0%} apart around ${centre:,.2f}" if moved < 0: return [Order(sell="USDG", buy="ETH", amount_usd=ORDER_USD * -moved, reason=why)] return [Order(sell="ETH", buy="USDG", amount_usd=ORDER_USD * moved, reason=why)]