"""Your strategy: the only file you need to edit. Every tick, bot.py reads prices and the wallet's balances, then calls decide(market, book, state). decide returns the orders it wants: a list of Order, or an empty list to do nothing. bot.py then checks each order against your limits in config.toml, and trades it (or, with dry_run on, only checks what the trade would do). Keep this file free of network calls, keys and sending: bot.py does all of that, inside the limits you set. What decide gets: market USD price of each token in config.toml's `tokens`, read from Chainlink on chain by the SDK: {"ETH": 2512.37, "USDG": 1.0}. USDG is $1. A token without a price feed is left out. book the wallet's balances: {"balances": {"ETH": {"units": 0.0398, "usd": 100.0}, "USDG": {"units": 102.5, "usd": 102.5}}, "total_usd": 202.5} "usd" is None for a token without a price, and total_usd adds up only the priced ones. state a dict that persists between ticks, and between runs (bot.py saves it to bot-state.json). Keep what you need in it: a running high, the last price, a counter. Use plain values (numbers, strings, lists, dicts). What decide returns: a list of Order(sell, buy, amount_usd, reason). `sell` and `buy` are names from `tokens` (e.g. "ETH", "USDG"), `amount_usd` is how many dollars of `sell` to trade, and `reason` is shown in the log. You don't have to size orders carefully: bot.py cuts every order to max_trade_usd, to what is left of max_day_usd, and to what the wallet holds, and skips anything under $1. """ 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 # --- Example 1 (the one that runs): keep a target split between ETH and USDG ------------------------------------------ # # The wallet should hold TARGET_ETH of its value in ETH and the rest in USDG. When ETH drifts more than BAND away from # that (it went up, so ETH is now too large a share; or it fell), the strategy trades back toward the target. # A wider band trades less often. TARGET_ETH = 0.50 # 50% of the wallet's value in ETH, 50% in USDG BAND = 0.05 # do nothing while ETH is within 45%..55% MIN_BOOK_USD = 5 # too little to bother with below this def decide(market, book, state): state["ticks"] = state.get("ticks", 0) + 1 # an example of state: how many ticks this bot has run # ------------------------------------------------------------------------------------------------------------------ # YOUR LOGIC GOES HERE. Replace the lines below with your own, or switch to Example 2 at the bottom of this file: # return decide_dip_buy(market, book, state) # ------------------------------------------------------------------------------------------------------------------ if "ETH" not in market: return [] # no ETH price this tick: wait for the next one total = book["total_usd"] if total < MIN_BOOK_USD: return [] eth_usd = book["balances"]["ETH"]["usd"] or 0 share = eth_usd / total state["last_eth_share"] = round(share, 4) drift = share - TARGET_ETH if abs(drift) <= BAND: return [] # inside the band: nothing to do usd = abs(drift) * total # the dollars that bring ETH back to its target why = f"ETH is {share:.0%} of ${total:,.2f}; the target is {TARGET_ETH:.0%}" if drift > 0: return [Order(sell="ETH", buy="USDG", amount_usd=usd, reason=why)] return [Order(sell="USDG", buy="ETH", amount_usd=usd, reason=why)] # --- Example 2: buy the dip ------------------------------------------------------------------------------------------- # # Remembers the highest ETH price it has seen (in state). When ETH falls DIP below that high, it buys BUY_USD of ETH # with USDG and starts measuring from the new, lower price. To use it, make decide() return # decide_dip_buy(market, book, state). DIP = 0.03 # buy after a 3% fall from the high BUY_USD = 5 # dollars of USDG to spend on each dip def decide_dip_buy(market, book, state): price = market.get("ETH") if price is None: return [] high = max(state.get("high", price), price) state["high"] = high if price > high * (1 - DIP): return [] # not down enough yet usdg = book["balances"].get("USDG", {}).get("usd") or 0 if usdg < BUY_USD: return [] # nothing left to buy with state["high"] = price # the next dip is measured from here return [Order(sell="USDG", buy="ETH", amount_usd=BUY_USD, reason=f"ETH ${price:,.2f} is {1 - price / high:.1%} under its high of ${high:,.2f}")]