/** * Strategy cookbook: DCA (dollar-cost averaging). A drop-in strategy.ts 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.ts and keep "dry_run": true in config.json until the log * shows what you expect. decide(market, book, state) gets what the starter bot's strategy.ts gets (prices, the wallet's * balances, and state kept in bot-state.json) and returns a list of orders. bot.ts cuts every order to your limits. * * An example to learn from, not advice: it makes no attempt to predict the price. */ export type Market = Record export type Holding = { units: number | null; usd: number | null } export type Book = { balances: Record; totalUsd: number } export type State = Record export type Order = { sell: string // the token to sell, e.g. 'ETH' buy: string // the token to buy, e.g. 'USDG' amountUsd: number // how many dollars of `sell` to trade reason?: string // why: shown in the console and bot-log.jsonl } const BUY_USD = 5 // dollars of USDG to spend on each buy const EVERY_HOURS = 24 // hours between buys: more hours, fewer buys export function decide(market: Market, book: Book, state: State): Order[] { const s = (state.dca ??= {}) // this strategy's own corner of state const price = market.ETH if (price === undefined) return [] // no ETH price this tick: wait for the next one const now = Date.now() / 1000 const last: number | undefined = s.lastBuy if (last !== undefined && now - last < EVERY_HOURS * 3600) return [] // not time yet if (usdOf(book, 'USDG') < BUY_USD) return [] // not enough USDG: nothing is recorded, so it tries next tick s.lastBuy = now s.buys = (s.buys ?? 0) + 1 return [{ sell: 'USDG', buy: 'ETH', amountUsd: BUY_USD, reason: `buy #${s.buys}: $${BUY_USD} of ETH at $${price.toFixed(2)}, every ${EVERY_HOURS} h` }] } /** The wallet's holding of `token` in dollars (0 when it holds none, or the token has no price). */ const usdOf = (book: Book, token: string) => book.balances[token]?.usd ?? 0