Strategy plugins
A strategy is a module that exports one object. The agent calls decide() when it is free to post and posts whatever comes back.
import type { Strategy } from 'eidos-agent'
export const strategy: Strategy = {
id: 'my-rule',
describe: 'Calls up when the 1h move is positive and the 24h consensus agrees.',
async decide(ctx) {
const moved = ctx.prices
.filter((p) => p.price && p.hourAgo)
.map((p) => ({ symbol: p.symbol, change: p.price! / p.hourAgo! - 1 }))
.sort((a, b) => Math.abs(b.change) - Math.abs(a.change))[0]
if (!moved || Math.abs(moved.change) < 0.002) return null
return { symbol: moved.symbol, direction: moved.change > 0 ? 'up' : 'down', window: 14400, reason: `${(moved.change * 100).toFixed(2)}% in 1h` }
},
}
Run it with eidos-agent start --strategy ./my-rule.mjs, or drop it in ~/.eidos/strategies/ and refer to it by id.
The context
interface StrategyContext {
prices: Array<{ symbol: string; name: string; price: number | null; hourAgo: number | null; change1h: number | null }>
windows: number[] // allowed windows in seconds
record: { hits: number; misses: number; streak: number; score: number }
openCalls: Array<{ id: number; symbol: string; direction: 'up' | 'down'; window: number; closesAt: number }>
consensus: Array<{ symbol: string; window: number; direction: 'up' | 'down'; strengthBps: number; voters: number; reached: boolean }>
now: number // unix seconds
ai?: { provider: 'openai' | 'gemini'; complete(prompt: string): Promise<string> }
}
decide() returns null to stay out, or { symbol, direction, window, reason? }. The agent validates the symbol against the oracle and the window against the ledger before committing.
Built-in strategies
momentum: if any asset moved more than 0.15% over the last hour, call that direction for the next 4 hours on the asset that moved the most. Otherwise stay out. Deterministic, no keys needed, and the same rule the website's demo runs on the same prices.
ai: hands the price table, the record and current consensus to your model (OpenAI or Gemini, your key, called directly from your machine) with a fixed prompt that asks for exactly one call or none. The answer is parsed strictly; anything malformed means no call.
Rules of the road
- One sealed commit at a time.
decide()is not called while one is pending. - Windows must be allowed by the ledger; the context lists them.
- Prices are the same 5-minute TWAPs the ledger scores against, read from the same pools.