"""Strategy cookbook: DCA (dollar-cost averaging). A drop-in strategy.py for the Orblivion starter bot. It buys BUY_USD of ETH with USDG every EVERY_HOURS hours, whatever the price. The first buy comes on the first tick with a price; the time of the last buy is kept in state, so a restart doesn't buy again early. 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. An example to learn from, not advice: it makes no attempt to predict the price. """ import time 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 BUY_USD = 5 # dollars of USDG to spend on each buy EVERY_HOURS = 24 # hours between buys: more hours, fewer buys def decide(market, book, state): s = state.setdefault("dca", {}) # 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 now = time.time() last = s.get("last_buy") if last is not None and now - last < EVERY_HOURS * 3600: return [] # not time yet if usd_of(book, "USDG") < BUY_USD: return [] # not enough USDG: nothing is recorded, so it tries next tick s["last_buy"] = now s["buys"] = s.get("buys", 0) + 1 return [Order(sell="USDG", buy="ETH", amount_usd=BUY_USD, reason=f"buy #{s['buys']}: ${BUY_USD:g} of ETH at ${price:,.2f}, every {EVERY_HOURS:g} h")] def usd_of(book, token): """The wallet's holding of `token` in dollars (0 when it holds none, or the token has no price).""" return (book["balances"].get(token) or {}).get("usd") or 0